@zephyr3d/editor 0.3.5 → 0.3.7
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/dist/assets/{index-DlnbO59X.js → index-ySiLtSxz.js} +2 -2
- package/dist/assistant/zephyr-types-index.json +14215 -7892
- package/dist/index.html +1 -1
- package/dist/modules/zephyr3d_backend-webgl.js.map +1 -1
- package/dist/modules/zephyr3d_backend-webgpu.js.map +1 -1
- package/dist/modules/zephyr3d_base.js +152 -152
- package/dist/modules/zephyr3d_base.js.map +1 -1
- package/dist/modules/zephyr3d_imgui.js.map +1 -1
- package/dist/modules/zephyr3d_loaders.js +6 -6
- package/dist/modules/zephyr3d_loaders.js.map +1 -1
- package/dist/modules/zephyr3d_scene.js +2538 -348
- package/dist/modules/zephyr3d_scene.js.map +1 -1
- package/dist/vendor/zephyr3d/backend-webgl/dist/index.js.map +1 -1
- package/dist/vendor/zephyr3d/backend-webgpu/dist/index.js.map +1 -1
- package/dist/vendor/zephyr3d/base/dist/vfs/common.js +152 -152
- package/dist/vendor/zephyr3d/base/dist/vfs/common.js.map +1 -1
- package/dist/vendor/zephyr3d/imgui/dist/index.d.ts +7 -0
- package/dist/vendor/zephyr3d/imgui/dist/index.js.map +1 -1
- package/dist/vendor/zephyr3d/loaders/dist/gltf/gltf_importer.js +6 -6
- package/dist/vendor/zephyr3d/loaders/dist/gltf/gltf_importer.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/animation/animation.js +87 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/animation.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/animation/animationcontroller.js +503 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/animationcontroller.js.map +1 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/animationset.js +782 -156
- package/dist/vendor/zephyr3d/scene/dist/animation/animationset.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/animation/animationtimeline.js +974 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/animationtimeline.js.map +1 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/joint_dynamics/convex_collider.js +320 -0
- package/dist/vendor/zephyr3d/scene/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
- package/dist/vendor/zephyr3d/scene/dist/app/scriptregistry.js +130 -125
- package/dist/vendor/zephyr3d/scene/dist/app/scriptregistry.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/asset/model.js +64 -63
- package/dist/vendor/zephyr3d/scene/dist/asset/model.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/index.d.ts +1270 -9
- package/dist/vendor/zephyr3d/scene/dist/index.js +3 -1
- package/dist/vendor/zephyr3d/scene/dist/index.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/material/mixins/lit.js +7 -3
- package/dist/vendor/zephyr3d/scene/dist/material/mixins/lit.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/manager.js +1 -0
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/manager.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/animation.js +4 -3
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/camera.js +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/node.js +3 -2
- package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/node.js.map +1 -1
- package/package.json +8 -8
|
@@ -24,6 +24,8 @@ import { Disposable } from '@zephyr3d/base';
|
|
|
24
24
|
/** @internal */ _tracks;
|
|
25
25
|
_weight;
|
|
26
26
|
/** @internal */ _skeletons;
|
|
27
|
+
/** @internal */ _frameRate;
|
|
28
|
+
/** @internal */ _markers;
|
|
27
29
|
/**
|
|
28
30
|
* Creates an animation instance
|
|
29
31
|
* @param name - Name of the animation
|
|
@@ -38,6 +40,8 @@ import { Disposable } from '@zephyr3d/base';
|
|
|
38
40
|
this._weight = 1;
|
|
39
41
|
this._autoPlay = false;
|
|
40
42
|
this._skeletons = new Set();
|
|
43
|
+
this._frameRate = 30;
|
|
44
|
+
this._markers = [];
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
43
47
|
* Whether this clip is embedded (owned inline by its container/resource).
|
|
@@ -88,6 +92,21 @@ import { Disposable } from '@zephyr3d/base';
|
|
|
88
92
|
this._skeletons = val;
|
|
89
93
|
}
|
|
90
94
|
/**
|
|
95
|
+
* Default frame rate used to convert frame-based marker references.
|
|
96
|
+
*/ get frameRate() {
|
|
97
|
+
return this._frameRate;
|
|
98
|
+
}
|
|
99
|
+
set frameRate(val) {
|
|
100
|
+
if (Number.isFinite(val) && val > 0) {
|
|
101
|
+
this._frameRate = val;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Timeline markers stored on this clip.
|
|
106
|
+
*/ get markers() {
|
|
107
|
+
return this._markers;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
91
110
|
* Total time span of the clip in seconds.
|
|
92
111
|
*
|
|
93
112
|
* Automatically extended when adding tracks with longer duration.
|
|
@@ -98,6 +117,74 @@ import { Disposable } from '@zephyr3d/base';
|
|
|
98
117
|
this._duration = val;
|
|
99
118
|
}
|
|
100
119
|
/**
|
|
120
|
+
* Add a serializable marker to this clip.
|
|
121
|
+
*
|
|
122
|
+
* @param marker - Marker metadata. If `id` is omitted, `name` is used as the id.
|
|
123
|
+
* @returns The normalized marker.
|
|
124
|
+
*/ addMarker(marker) {
|
|
125
|
+
if (!marker?.name) {
|
|
126
|
+
console.error('Animation marker must have a name');
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const normalized = {
|
|
130
|
+
...marker,
|
|
131
|
+
id: marker.id || marker.name
|
|
132
|
+
};
|
|
133
|
+
if (this.resolveMarkerTime(normalized) === null) {
|
|
134
|
+
console.error('Animation marker must have either time or frame');
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
this._markers.push(normalized);
|
|
138
|
+
this._markers.sort((a, b)=>(this.resolveMarkerTime(a) ?? 0) - (this.resolveMarkerTime(b) ?? 0));
|
|
139
|
+
return normalized;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Remove markers matching the marker id or name.
|
|
143
|
+
*/ removeMarker(idOrName) {
|
|
144
|
+
const oldLength = this._markers.length;
|
|
145
|
+
this._markers = this._markers.filter((marker)=>marker.id !== idOrName && marker.name !== idOrName);
|
|
146
|
+
return this._markers.length !== oldLength;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get the first marker matching the marker id or name.
|
|
150
|
+
*/ getMarker(idOrName) {
|
|
151
|
+
return this._markers.find((marker)=>marker.id === idOrName || marker.name === idOrName) ?? null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Resolve a marker to seconds on this clip's timeline.
|
|
155
|
+
*/ resolveMarkerTime(marker) {
|
|
156
|
+
if (typeof marker.time === 'number' && Number.isFinite(marker.time)) {
|
|
157
|
+
return marker.time;
|
|
158
|
+
}
|
|
159
|
+
if (typeof marker.frame === 'number' && Number.isFinite(marker.frame)) {
|
|
160
|
+
const fps = marker.fps ?? this._frameRate;
|
|
161
|
+
return fps > 0 ? marker.frame / fps : null;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve a time reference to seconds on this clip's timeline.
|
|
167
|
+
*/ resolveTimeRef(ref) {
|
|
168
|
+
if (ref === null || ref === undefined) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (typeof ref === 'number') {
|
|
172
|
+
return Number.isFinite(ref) ? ref : null;
|
|
173
|
+
}
|
|
174
|
+
if ('time' in ref) {
|
|
175
|
+
return Number.isFinite(ref.time) ? ref.time : null;
|
|
176
|
+
}
|
|
177
|
+
if ('frame' in ref) {
|
|
178
|
+
const fps = ref.fps ?? this._frameRate;
|
|
179
|
+
return Number.isFinite(ref.frame) && fps > 0 ? ref.frame / fps : null;
|
|
180
|
+
}
|
|
181
|
+
if ('marker' in ref) {
|
|
182
|
+
const marker = this.getMarker(ref.marker);
|
|
183
|
+
return marker ? this.resolveMarkerTime(marker) : null;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
101
188
|
* Add a skeleton used by this clip.
|
|
102
189
|
*
|
|
103
190
|
* @param skeletonId - Persistent ID of Skeleton to register for this clip.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animation.js","sources":["../../src/animation/animation.ts"],"sourcesContent":["import { Disposable } from '@zephyr3d/base';\r\nimport type { AnimationSet } from './animationset';\r\nimport type { AnimationTrack } from './animationtrack';\r\n\r\n/**\r\n * Animation clip\r\n *\r\n * Represents a named animation composed of multiple tracks targeting various objects/properties,\r\n * with an overall duration, weight, and optional auto-play behavior. Tracks may target different\r\n * objects and are grouped by a per-target array. Skeletons used by this clip are tracked for\r\n * lifecycle and application during playback.\r\n *\r\n * Typical workflow:\r\n * - Create a clip via `AnimationSet.createAnimation(name)`.\r\n * - Add one or more `AnimationTrack`s via `addTrack(target, track)`.\r\n * - Optionally register skeletons via `addSkeleton(...)`.\r\n * - Start playback through the owning `AnimationSet.playAnimation(name, options)`.\r\n *\r\n * @public\r\n */\r\nexport class AnimationClip extends Disposable {\r\n /** @internal */\r\n protected _name: string;\r\n /** @internal */\r\n protected _animationSet: AnimationSet;\r\n /** @internal */\r\n protected _embedded: boolean;\r\n /** @internal */\r\n protected _duration: number;\r\n /** @internal */\r\n protected _autoPlay: boolean;\r\n /** @internal */\r\n protected _tracks: Map<object, AnimationTrack[]>;\r\n protected _weight: number;\r\n /** @internal */\r\n protected _skeletons: Set<string>;\r\n /**\r\n * Creates an animation instance\r\n * @param name - Name of the animation\r\n * @param model - Parent node if this is a skeleton animation\r\n */\r\n constructor(name: string, animationSet: AnimationSet, embedded = false) {\r\n super();\r\n this._name = name;\r\n this._animationSet = animationSet;\r\n this._embedded = embedded;\r\n this._tracks = new Map();\r\n this._duration = 0;\r\n this._weight = 1;\r\n this._autoPlay = false;\r\n this._skeletons = new Set();\r\n }\r\n /**\r\n * Whether this clip is embedded (owned inline by its container/resource).\r\n */\r\n get embedded() {\r\n return this._embedded;\r\n }\r\n /**\r\n * The `AnimationSet` that owns this clip.\r\n */\r\n get animationSet() {\r\n return this._animationSet;\r\n }\r\n /**\r\n * Global blend weight for the clip.\r\n *\r\n * Used by the animation system when combining multiple active clips.\r\n */\r\n get weight() {\r\n return this._weight;\r\n }\r\n set weight(val: number) {\r\n this._weight = val;\r\n }\r\n /**\r\n * Whether this clip should start playing automatically when loaded/instantiated.\r\n */\r\n get autoPlay() {\r\n return this._autoPlay;\r\n }\r\n set autoPlay(val: boolean) {\r\n this._autoPlay = val;\r\n }\r\n /**\r\n * The unique name of this clip.\r\n */\r\n get name() {\r\n return this._name;\r\n }\r\n /**\r\n * All animation tracks grouped by target object.\r\n *\r\n * Key: target object; Value: list of `AnimationTrack`s affecting that target.\r\n */\r\n get tracks() {\r\n return this._tracks;\r\n }\r\n /**\r\n * All skeletons referenced by this clip.\r\n */\r\n get skeletons() {\r\n return this._skeletons;\r\n }\r\n set skeletons(val: Set<string>) {\r\n this._skeletons = val;\r\n }\r\n /**\r\n * Total time span of the clip in seconds.\r\n *\r\n * Automatically extended when adding tracks with longer duration.\r\n */\r\n get timeDuration() {\r\n return this._duration;\r\n }\r\n set timeDuration(val) {\r\n this._duration = val;\r\n }\r\n /**\r\n * Add a skeleton used by this clip.\r\n *\r\n * @param skeletonId - Persistent ID of Skeleton to register for this clip.\r\n */\r\n addSkeleton(skeletonId: string) {\r\n this._skeletons.add(skeletonId);\r\n }\r\n /**\r\n * Remove a specific track from this clip.\r\n *\r\n * Errors if the track does not belong to this clip. Does nothing if not found.\r\n *\r\n * @param track - The track instance to remove.\r\n * @returns This clip (for chaining).\r\n */\r\n deleteTrack(track: AnimationTrack) {\r\n if (track?.animation !== this) {\r\n console.error('Cannot delete animation track which is not belongs to THIS animation');\r\n }\r\n for (const k of this._tracks.keys()) {\r\n const tracks = this._tracks.get(k)!;\r\n const index = tracks.indexOf(track);\r\n if (index >= 0) {\r\n tracks.splice(index, 1);\r\n }\r\n }\r\n return this;\r\n }\r\n /**\r\n * Add a track to this clip for a specific target object.\r\n *\r\n * Constraints:\r\n * - The track must not already belong to another clip.\r\n * - Only one track with the same blendId may exist per target in a single clip.\r\n *\r\n * Side effects:\r\n * - Assigns this clip to `track.animation`.\r\n * - Extends `timeDuration` to cover the track duration if longer.\r\n * - Calls `track.reset(target)` to initialize the target state if needed.\r\n *\r\n * @param target - Target object controlled by the track.\r\n * @param track - Track to add.\r\n * @returns This clip (for chaining).\r\n */\r\n addTrack(target: object, track: AnimationTrack) {\r\n if (!track) {\r\n return this;\r\n }\r\n if (track.animation) {\r\n if (track.animation === this) {\r\n return this;\r\n } else {\r\n console.error('Track is already in another animation');\r\n return this;\r\n }\r\n }\r\n const blendId = track.getBlendId();\r\n const tracks = this._tracks.get(target);\r\n if (tracks && tracks.findIndex((track) => track.getBlendId() === blendId) >= 0) {\r\n console.error('Tracks with same BlendId could not be added to same animation');\r\n return this;\r\n }\r\n track.animation = this;\r\n let trackInfo = this._tracks.get(target);\r\n if (!trackInfo) {\r\n trackInfo = [];\r\n this._tracks.set(target, trackInfo);\r\n }\r\n trackInfo.push(track);\r\n this._duration = Math.max(this._duration, track.getDuration() ?? 0);\r\n track.reset(target);\r\n return this;\r\n }\r\n /*\r\n resample(frames: number, callback: (frame: number) => void) {\r\n for (let frame = 0; frame <= frames; frame++) {\r\n const t = frame / this.timeDuration;\r\n for (const [k, v] of this.tracks) {\r\n for (const track of v) {\r\n const state = track.calculateState(k, t);\r\n track.applyState(k, state);\r\n }\r\n }\r\n for (const sk of this.skeletons) {\r\n const skeleton = this._animationSet.model.findSkeletonById(sk);\r\n if (skeleton) {\r\n skeleton.computeJoints();\r\n }\r\n }\r\n callback(frame);\r\n }\r\n }\r\n */\r\n}\r\n"],"names":["AnimationClip","Disposable","_weight","name","animationSet","embedded","_name","_animationSet","_embedded","_tracks","Map","_duration","_autoPlay","_skeletons","Set","weight","val","autoPlay","tracks","skeletons","timeDuration","addSkeleton","skeletonId","add","deleteTrack","track","animation","console","error","k","keys","get","index","indexOf","splice","addTrack","target","blendId","getBlendId","findIndex","trackInfo","set","push","Math","max","getDuration","reset"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"animation.js","sources":["../../src/animation/animation.ts"],"sourcesContent":["import { Disposable } from '@zephyr3d/base';\r\nimport type { AnimationSet } from './animationset';\r\nimport type { AnimationTrack } from './animationtrack';\r\n\r\n/**\r\n * A serializable reference to a point on an animation clip timeline.\r\n * @public\r\n */\r\nexport type AnimationTimeRef =\r\n | number\r\n | {\r\n time: number;\r\n }\r\n | {\r\n frame: number;\r\n fps?: number;\r\n }\r\n | {\r\n marker: string;\r\n };\r\n\r\n/**\r\n * Timeline marker metadata stored on an animation clip.\r\n *\r\n * Markers are data, not callbacks. Runtime systems may dispatch events when a\r\n * playback cursor crosses the marker.\r\n * @public\r\n */\r\nexport type AnimationMarker = {\r\n /** Stable marker id for editor and blueprint references. */\r\n id?: string;\r\n /** Display/event name. */\r\n name: string;\r\n /** Marker time in seconds. */\r\n time?: number;\r\n /** Marker frame. Converted with `fps` or the owning clip's `frameRate`. */\r\n frame?: number;\r\n /** Optional frame rate used for `frame` conversion. */\r\n fps?: number;\r\n /** Serializable user payload. */\r\n payload?: unknown;\r\n};\r\n\r\n/**\r\n * Animation clip\r\n *\r\n * Represents a named animation composed of multiple tracks targeting various objects/properties,\r\n * with an overall duration, weight, and optional auto-play behavior. Tracks may target different\r\n * objects and are grouped by a per-target array. Skeletons used by this clip are tracked for\r\n * lifecycle and application during playback.\r\n *\r\n * Typical workflow:\r\n * - Create a clip via `AnimationSet.createAnimation(name)`.\r\n * - Add one or more `AnimationTrack`s via `addTrack(target, track)`.\r\n * - Optionally register skeletons via `addSkeleton(...)`.\r\n * - Start playback through the owning `AnimationSet.playAnimation(name, options)`.\r\n *\r\n * @public\r\n */\r\nexport class AnimationClip extends Disposable {\r\n /** @internal */\r\n protected _name: string;\r\n /** @internal */\r\n protected _animationSet: AnimationSet;\r\n /** @internal */\r\n protected _embedded: boolean;\r\n /** @internal */\r\n protected _duration: number;\r\n /** @internal */\r\n protected _autoPlay: boolean;\r\n /** @internal */\r\n protected _tracks: Map<object, AnimationTrack[]>;\r\n protected _weight: number;\r\n /** @internal */\r\n protected _skeletons: Set<string>;\r\n /** @internal */\r\n protected _frameRate: number;\r\n /** @internal */\r\n protected _markers: AnimationMarker[];\r\n /**\r\n * Creates an animation instance\r\n * @param name - Name of the animation\r\n * @param model - Parent node if this is a skeleton animation\r\n */\r\n constructor(name: string, animationSet: AnimationSet, embedded = false) {\r\n super();\r\n this._name = name;\r\n this._animationSet = animationSet;\r\n this._embedded = embedded;\r\n this._tracks = new Map();\r\n this._duration = 0;\r\n this._weight = 1;\r\n this._autoPlay = false;\r\n this._skeletons = new Set();\r\n this._frameRate = 30;\r\n this._markers = [];\r\n }\r\n /**\r\n * Whether this clip is embedded (owned inline by its container/resource).\r\n */\r\n get embedded() {\r\n return this._embedded;\r\n }\r\n /**\r\n * The `AnimationSet` that owns this clip.\r\n */\r\n get animationSet() {\r\n return this._animationSet;\r\n }\r\n /**\r\n * Global blend weight for the clip.\r\n *\r\n * Used by the animation system when combining multiple active clips.\r\n */\r\n get weight() {\r\n return this._weight;\r\n }\r\n set weight(val: number) {\r\n this._weight = val;\r\n }\r\n /**\r\n * Whether this clip should start playing automatically when loaded/instantiated.\r\n */\r\n get autoPlay() {\r\n return this._autoPlay;\r\n }\r\n set autoPlay(val: boolean) {\r\n this._autoPlay = val;\r\n }\r\n /**\r\n * The unique name of this clip.\r\n */\r\n get name() {\r\n return this._name;\r\n }\r\n /**\r\n * All animation tracks grouped by target object.\r\n *\r\n * Key: target object; Value: list of `AnimationTrack`s affecting that target.\r\n */\r\n get tracks() {\r\n return this._tracks;\r\n }\r\n /**\r\n * All skeletons referenced by this clip.\r\n */\r\n get skeletons() {\r\n return this._skeletons;\r\n }\r\n set skeletons(val: Set<string>) {\r\n this._skeletons = val;\r\n }\r\n /**\r\n * Default frame rate used to convert frame-based marker references.\r\n */\r\n get frameRate() {\r\n return this._frameRate;\r\n }\r\n set frameRate(val: number) {\r\n if (Number.isFinite(val) && val > 0) {\r\n this._frameRate = val;\r\n }\r\n }\r\n /**\r\n * Timeline markers stored on this clip.\r\n */\r\n get markers() {\r\n return this._markers;\r\n }\r\n /**\r\n * Total time span of the clip in seconds.\r\n *\r\n * Automatically extended when adding tracks with longer duration.\r\n */\r\n get timeDuration() {\r\n return this._duration;\r\n }\r\n set timeDuration(val) {\r\n this._duration = val;\r\n }\r\n /**\r\n * Add a serializable marker to this clip.\r\n *\r\n * @param marker - Marker metadata. If `id` is omitted, `name` is used as the id.\r\n * @returns The normalized marker.\r\n */\r\n addMarker(marker: AnimationMarker): AnimationMarker | null {\r\n if (!marker?.name) {\r\n console.error('Animation marker must have a name');\r\n return null;\r\n }\r\n const normalized: AnimationMarker = {\r\n ...marker,\r\n id: marker.id || marker.name\r\n };\r\n if (this.resolveMarkerTime(normalized) === null) {\r\n console.error('Animation marker must have either time or frame');\r\n return null;\r\n }\r\n this._markers.push(normalized);\r\n this._markers.sort((a, b) => (this.resolveMarkerTime(a) ?? 0) - (this.resolveMarkerTime(b) ?? 0));\r\n return normalized;\r\n }\r\n /**\r\n * Remove markers matching the marker id or name.\r\n */\r\n removeMarker(idOrName: string) {\r\n const oldLength = this._markers.length;\r\n this._markers = this._markers.filter((marker) => marker.id !== idOrName && marker.name !== idOrName);\r\n return this._markers.length !== oldLength;\r\n }\r\n /**\r\n * Get the first marker matching the marker id or name.\r\n */\r\n getMarker(idOrName: string) {\r\n return this._markers.find((marker) => marker.id === idOrName || marker.name === idOrName) ?? null;\r\n }\r\n /**\r\n * Resolve a marker to seconds on this clip's timeline.\r\n */\r\n resolveMarkerTime(marker: AnimationMarker): number | null {\r\n if (typeof marker.time === 'number' && Number.isFinite(marker.time)) {\r\n return marker.time;\r\n }\r\n if (typeof marker.frame === 'number' && Number.isFinite(marker.frame)) {\r\n const fps = marker.fps ?? this._frameRate;\r\n return fps > 0 ? marker.frame / fps : null;\r\n }\r\n return null;\r\n }\r\n /**\r\n * Resolve a time reference to seconds on this clip's timeline.\r\n */\r\n resolveTimeRef(ref: AnimationTimeRef | null | undefined): number | null {\r\n if (ref === null || ref === undefined) {\r\n return null;\r\n }\r\n if (typeof ref === 'number') {\r\n return Number.isFinite(ref) ? ref : null;\r\n }\r\n if ('time' in ref) {\r\n return Number.isFinite(ref.time) ? ref.time : null;\r\n }\r\n if ('frame' in ref) {\r\n const fps = ref.fps ?? this._frameRate;\r\n return Number.isFinite(ref.frame) && fps > 0 ? ref.frame / fps : null;\r\n }\r\n if ('marker' in ref) {\r\n const marker = this.getMarker(ref.marker);\r\n return marker ? this.resolveMarkerTime(marker) : null;\r\n }\r\n return null;\r\n }\r\n /**\r\n * Add a skeleton used by this clip.\r\n *\r\n * @param skeletonId - Persistent ID of Skeleton to register for this clip.\r\n */\r\n addSkeleton(skeletonId: string) {\r\n this._skeletons.add(skeletonId);\r\n }\r\n /**\r\n * Remove a specific track from this clip.\r\n *\r\n * Errors if the track does not belong to this clip. Does nothing if not found.\r\n *\r\n * @param track - The track instance to remove.\r\n * @returns This clip (for chaining).\r\n */\r\n deleteTrack(track: AnimationTrack) {\r\n if (track?.animation !== this) {\r\n console.error('Cannot delete animation track which is not belongs to THIS animation');\r\n }\r\n for (const k of this._tracks.keys()) {\r\n const tracks = this._tracks.get(k)!;\r\n const index = tracks.indexOf(track);\r\n if (index >= 0) {\r\n tracks.splice(index, 1);\r\n }\r\n }\r\n return this;\r\n }\r\n /**\r\n * Add a track to this clip for a specific target object.\r\n *\r\n * Constraints:\r\n * - The track must not already belong to another clip.\r\n * - Only one track with the same blendId may exist per target in a single clip.\r\n *\r\n * Side effects:\r\n * - Assigns this clip to `track.animation`.\r\n * - Extends `timeDuration` to cover the track duration if longer.\r\n * - Calls `track.reset(target)` to initialize the target state if needed.\r\n *\r\n * @param target - Target object controlled by the track.\r\n * @param track - Track to add.\r\n * @returns This clip (for chaining).\r\n */\r\n addTrack(target: object, track: AnimationTrack) {\r\n if (!track) {\r\n return this;\r\n }\r\n if (track.animation) {\r\n if (track.animation === this) {\r\n return this;\r\n } else {\r\n console.error('Track is already in another animation');\r\n return this;\r\n }\r\n }\r\n const blendId = track.getBlendId();\r\n const tracks = this._tracks.get(target);\r\n if (tracks && tracks.findIndex((track) => track.getBlendId() === blendId) >= 0) {\r\n console.error('Tracks with same BlendId could not be added to same animation');\r\n return this;\r\n }\r\n track.animation = this;\r\n let trackInfo = this._tracks.get(target);\r\n if (!trackInfo) {\r\n trackInfo = [];\r\n this._tracks.set(target, trackInfo);\r\n }\r\n trackInfo.push(track);\r\n this._duration = Math.max(this._duration, track.getDuration() ?? 0);\r\n track.reset(target);\r\n return this;\r\n }\r\n /*\r\n resample(frames: number, callback: (frame: number) => void) {\r\n for (let frame = 0; frame <= frames; frame++) {\r\n const t = frame / this.timeDuration;\r\n for (const [k, v] of this.tracks) {\r\n for (const track of v) {\r\n const state = track.calculateState(k, t);\r\n track.applyState(k, state);\r\n }\r\n }\r\n for (const sk of this.skeletons) {\r\n const skeleton = this._animationSet.model.findSkeletonById(sk);\r\n if (skeleton) {\r\n skeleton.computeJoints();\r\n }\r\n }\r\n callback(frame);\r\n }\r\n }\r\n */\r\n}\r\n"],"names":["AnimationClip","Disposable","_weight","name","animationSet","embedded","_name","_animationSet","_embedded","_tracks","Map","_duration","_autoPlay","_skeletons","Set","_frameRate","_markers","weight","val","autoPlay","tracks","skeletons","frameRate","Number","isFinite","markers","timeDuration","addMarker","marker","console","error","normalized","id","resolveMarkerTime","push","sort","a","b","removeMarker","idOrName","oldLength","length","filter","getMarker","find","time","frame","fps","resolveTimeRef","ref","undefined","addSkeleton","skeletonId","add","deleteTrack","track","animation","k","keys","get","index","indexOf","splice","addTrack","target","blendId","getBlendId","findIndex","trackInfo","set","Math","max","getDuration","reset"],"mappings":"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"}
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import { Observable } from '@zephyr3d/base';
|
|
2
|
+
import { AnimationTimeline } from './animationtimeline.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Event-driven animation state controller.
|
|
6
|
+
*
|
|
7
|
+
* Each state owns one serializable timeline. External gameplay events are dispatched to the
|
|
8
|
+
* current timeline first, then to the current state's response table, so different states can
|
|
9
|
+
* respond to the same event differently.
|
|
10
|
+
* @public
|
|
11
|
+
*/ class AnimationController extends Observable {
|
|
12
|
+
/**
|
|
13
|
+
* Animation set used to create playbacks for all state timelines.
|
|
14
|
+
*/ animationSet;
|
|
15
|
+
_states;
|
|
16
|
+
_currentState;
|
|
17
|
+
_runner;
|
|
18
|
+
_onRunnerComplete;
|
|
19
|
+
_onRunnerEmit;
|
|
20
|
+
/**
|
|
21
|
+
* Create a controller for an animation set.
|
|
22
|
+
*
|
|
23
|
+
* @param animationSet - Animation set that owns the clips and active playbacks.
|
|
24
|
+
*/ constructor(animationSet){
|
|
25
|
+
super();
|
|
26
|
+
this.animationSet = animationSet;
|
|
27
|
+
this._states = new Map();
|
|
28
|
+
this._currentState = null;
|
|
29
|
+
this._runner = null;
|
|
30
|
+
this._onRunnerComplete = null;
|
|
31
|
+
this._onRunnerEmit = null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Current state name.
|
|
35
|
+
*
|
|
36
|
+
* @returns The active state name, or null when the controller is stopped or has not entered a state.
|
|
37
|
+
*/ get currentState() {
|
|
38
|
+
return this._currentState;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Current timeline runner.
|
|
42
|
+
*
|
|
43
|
+
* @returns The active timeline runner, or null when no state is running.
|
|
44
|
+
*/ get runner() {
|
|
45
|
+
return this._runner;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Register or replace a named state definition.
|
|
49
|
+
*
|
|
50
|
+
* @param name - Unique state name.
|
|
51
|
+
* @param definition - Timeline and event response configuration for the state.
|
|
52
|
+
* @returns This controller for chaining.
|
|
53
|
+
*/ addState(name, definition) {
|
|
54
|
+
if (this._states.has(name)) {
|
|
55
|
+
console.warn(`AnimationController state ${name} already exists; overwriting`);
|
|
56
|
+
}
|
|
57
|
+
this._states.set(name, definition);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Test whether a state has been registered.
|
|
62
|
+
*
|
|
63
|
+
* @param name - State name to look up.
|
|
64
|
+
* @returns True if the controller contains a state with the given name; otherwise false.
|
|
65
|
+
*/ hasState(name) {
|
|
66
|
+
return this._states.has(name);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Enter a registered state.
|
|
70
|
+
*
|
|
71
|
+
* If the requested state is already current and `options.force` is not set, this returns the
|
|
72
|
+
* existing runner without restarting the timeline. When a transition duration is provided, the
|
|
73
|
+
* previous runner fades out while the entry plays of the new state fade in.
|
|
74
|
+
*
|
|
75
|
+
* @param name - State name to enter.
|
|
76
|
+
* @param options - Optional transition, re-entry, and stop behavior.
|
|
77
|
+
* @returns The active runner for the entered state, or null if the state does not exist.
|
|
78
|
+
*/ setState(name, options) {
|
|
79
|
+
const definition = this._states.get(name);
|
|
80
|
+
if (!definition) {
|
|
81
|
+
console.error(`AnimationController state ${name} not exists`);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (!options?.force && this._currentState === name) {
|
|
85
|
+
return this._runner;
|
|
86
|
+
}
|
|
87
|
+
const previousState = this._currentState;
|
|
88
|
+
const transition = Math.max(options?.transition ?? definition.transition ?? 0, 0);
|
|
89
|
+
const returnTarget = this.resolveReturnTarget(options?.returnTo, previousState, options?.returnTransition);
|
|
90
|
+
this.detachRunner();
|
|
91
|
+
if (transition > 0) {
|
|
92
|
+
this._runner?.stop({
|
|
93
|
+
fadeOut: transition,
|
|
94
|
+
reason: 'interrupted'
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
this._runner?.stop(options?.stop ?? {
|
|
98
|
+
reason: 'interrupted'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const timeline = new AnimationTimeline(prepareTimelineForTransition(definition.timeline, transition, returnTarget?.transition, options?.sync));
|
|
102
|
+
this._runner = timeline.createRunner(this.animationSet);
|
|
103
|
+
const runner = this._runner;
|
|
104
|
+
this.attachRunner(runner, name, returnTarget);
|
|
105
|
+
// Enter the new state *before* starting the runner: start() flushes synchronously, so any
|
|
106
|
+
// initial `emit`/`statecomplete` must observe the controller already in `name`. Otherwise a
|
|
107
|
+
// listener calling dispatch() would route against the previous state.
|
|
108
|
+
this._currentState = name;
|
|
109
|
+
this.dispatchEvent('statechange', name, previousState);
|
|
110
|
+
// The statechange listener may have re-entered (stop() or another setState()), swapping out or
|
|
111
|
+
// clearing `_runner`. Only start the runner we created if it is still the active one.
|
|
112
|
+
if (this._runner === runner && this._currentState === name) {
|
|
113
|
+
runner.start();
|
|
114
|
+
}
|
|
115
|
+
// If a reentrant listener superseded us, `_runner` is the now-active runner (or null after a
|
|
116
|
+
// reentrant stop()); return that rather than a runner we never started.
|
|
117
|
+
return this._runner;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Dispatch a gameplay event to the active state.
|
|
121
|
+
*
|
|
122
|
+
* The active timeline receives the event first. If it does not handle the event, the current
|
|
123
|
+
* state's response table may consume it, enqueue or run steps, or transition to another state.
|
|
124
|
+
*
|
|
125
|
+
* @param event - Event name to dispatch.
|
|
126
|
+
* @param payload - Optional event payload passed through result notifications.
|
|
127
|
+
* @returns The resolved handling result for the event.
|
|
128
|
+
*/ dispatch(event, payload) {
|
|
129
|
+
if (!this._currentState || !this._runner) {
|
|
130
|
+
return this.emitResult({
|
|
131
|
+
handled: false,
|
|
132
|
+
policy: 'none',
|
|
133
|
+
event,
|
|
134
|
+
payload
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const timelineResult = this._runner.dispatch(event, payload);
|
|
138
|
+
if (timelineResult.handled) {
|
|
139
|
+
return this.emitResult(timelineResult);
|
|
140
|
+
}
|
|
141
|
+
const response = this._states.get(this._currentState)?.responses?.find((item)=>item.event === event);
|
|
142
|
+
if (!response || response.target.ignore) {
|
|
143
|
+
return this.emitResult({
|
|
144
|
+
handled: false,
|
|
145
|
+
policy: response ? 'ignore' : 'none',
|
|
146
|
+
event,
|
|
147
|
+
payload
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
if (response.target.consume) {
|
|
151
|
+
return this.emitResult({
|
|
152
|
+
handled: true,
|
|
153
|
+
policy: 'consume',
|
|
154
|
+
event,
|
|
155
|
+
payload
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (response.target.targetState !== undefined) {
|
|
159
|
+
const transition = typeof response.onActive === 'object' ? response.onActive.fadeOut : undefined;
|
|
160
|
+
const runner = this.setState(response.target.targetState, {
|
|
161
|
+
transition,
|
|
162
|
+
returnTo: response.target.returnTo,
|
|
163
|
+
returnTransition: response.target.returnTransition
|
|
164
|
+
});
|
|
165
|
+
// setState returns null when the target state is not registered: a config error must not be
|
|
166
|
+
// reported as a successfully handled transition.
|
|
167
|
+
if (!runner) {
|
|
168
|
+
return this.emitResult({
|
|
169
|
+
handled: false,
|
|
170
|
+
policy: 'transition',
|
|
171
|
+
event,
|
|
172
|
+
payload
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
return this.emitResult({
|
|
176
|
+
handled: true,
|
|
177
|
+
policy: 'transition',
|
|
178
|
+
event,
|
|
179
|
+
payload
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
const steps = response.target.steps;
|
|
183
|
+
if (!steps?.length) {
|
|
184
|
+
return this.emitResult({
|
|
185
|
+
handled: false,
|
|
186
|
+
policy: 'none',
|
|
187
|
+
event,
|
|
188
|
+
payload
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (response.enqueue) {
|
|
192
|
+
this._runner.enqueue(steps);
|
|
193
|
+
return this.emitResult({
|
|
194
|
+
handled: true,
|
|
195
|
+
policy: 'enqueue',
|
|
196
|
+
event,
|
|
197
|
+
payload
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
const disposition = response.onActive ?? 'stop';
|
|
201
|
+
if (disposition === 'keep') {
|
|
202
|
+
this._runner.runConcurrent(steps);
|
|
203
|
+
} else {
|
|
204
|
+
const stopOptions = typeof disposition === 'object' ? {
|
|
205
|
+
...disposition,
|
|
206
|
+
reason: 'interrupted'
|
|
207
|
+
} : undefined;
|
|
208
|
+
this.detachRunner();
|
|
209
|
+
this._runner.stop(stopOptions);
|
|
210
|
+
this._runner = new AnimationTimeline(steps).createRunner(this.animationSet);
|
|
211
|
+
this.attachRunner(this._runner, this._currentState);
|
|
212
|
+
this._runner.start();
|
|
213
|
+
}
|
|
214
|
+
return this.emitResult({
|
|
215
|
+
handled: true,
|
|
216
|
+
policy: 'steps',
|
|
217
|
+
event,
|
|
218
|
+
payload
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Stop the active state and clear the current state.
|
|
223
|
+
*
|
|
224
|
+
* @param options - Optional stop behavior applied to playbacks owned by the active runner.
|
|
225
|
+
* @returns void
|
|
226
|
+
*/ stop(options) {
|
|
227
|
+
const previousState = this._currentState;
|
|
228
|
+
this.detachRunner();
|
|
229
|
+
this._runner?.stop(options);
|
|
230
|
+
this._runner = null;
|
|
231
|
+
this._currentState = null;
|
|
232
|
+
if (previousState !== null) {
|
|
233
|
+
this.dispatchEvent('statechange', null, previousState);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Stop playback and remove all registered states.
|
|
238
|
+
*
|
|
239
|
+
* @returns void
|
|
240
|
+
*/ dispose() {
|
|
241
|
+
this.stop();
|
|
242
|
+
this._states.clear();
|
|
243
|
+
}
|
|
244
|
+
emitResult(result) {
|
|
245
|
+
this.dispatchEvent('event', result.event, result.payload, result);
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
resolveReturnTarget(returnTo, previousState, returnTransition) {
|
|
249
|
+
if (returnTo === undefined) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
const state = returnTo === true ? previousState : returnTo;
|
|
253
|
+
if (!state) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const definition = this._states.get(state);
|
|
257
|
+
if (!definition) {
|
|
258
|
+
console.error(`AnimationController return state ${state} not exists`);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
state,
|
|
263
|
+
transition: returnTransition ?? definition.transition
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
attachRunner(runner, state, returnTarget) {
|
|
267
|
+
this._onRunnerComplete = ()=>{
|
|
268
|
+
this.dispatchEvent('statecomplete', state);
|
|
269
|
+
if (returnTarget && this._runner === runner && this._currentState === state) {
|
|
270
|
+
const sync = returnTarget.transition && runner.lastCompletedPlaybackId ? {
|
|
271
|
+
target: runner.lastCompletedPlaybackId,
|
|
272
|
+
mode: 'normalized'
|
|
273
|
+
} : undefined;
|
|
274
|
+
this.setState(returnTarget.state, {
|
|
275
|
+
transition: returnTarget.transition,
|
|
276
|
+
sync
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
this._onRunnerEmit = (event, payload)=>{
|
|
281
|
+
this.dispatchEvent('emit', event, payload);
|
|
282
|
+
};
|
|
283
|
+
runner.on('complete', this._onRunnerComplete);
|
|
284
|
+
runner.on('emit', this._onRunnerEmit);
|
|
285
|
+
}
|
|
286
|
+
detachRunner() {
|
|
287
|
+
if (this._runner) {
|
|
288
|
+
if (this._onRunnerComplete) {
|
|
289
|
+
this._runner.off('complete', this._onRunnerComplete);
|
|
290
|
+
}
|
|
291
|
+
if (this._onRunnerEmit) {
|
|
292
|
+
this._runner.off('emit', this._onRunnerEmit);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
this._onRunnerComplete = null;
|
|
296
|
+
this._onRunnerEmit = null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function prepareTimelineForTransition(definition, entryTransition, returnTransition, entrySync) {
|
|
300
|
+
let timeline = definition;
|
|
301
|
+
if (entrySync) {
|
|
302
|
+
timeline = withEntrySync(timeline, entrySync);
|
|
303
|
+
}
|
|
304
|
+
const completionFadeOut = Math.max(returnTransition ?? 0, 0);
|
|
305
|
+
if (completionFadeOut > 0) {
|
|
306
|
+
timeline = withCompletionFadeOut(timeline, completionFadeOut);
|
|
307
|
+
}
|
|
308
|
+
return entryTransition > 0 ? withFadeIn(timeline, entryTransition) : timeline;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Return a copy of the timeline definition whose entry plays use `sync` unless they already
|
|
312
|
+
* declare a more specific synchronization source.
|
|
313
|
+
*/ function withEntrySync(definition, sync) {
|
|
314
|
+
const def = Array.isArray(definition) ? {
|
|
315
|
+
steps: definition
|
|
316
|
+
} : definition;
|
|
317
|
+
return {
|
|
318
|
+
steps: injectEntrySync(def.steps, sync).steps,
|
|
319
|
+
responses: def.responses
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function injectEntrySync(steps, sync) {
|
|
323
|
+
let blocked = false;
|
|
324
|
+
const out = steps.map((step)=>{
|
|
325
|
+
if (blocked) {
|
|
326
|
+
return step;
|
|
327
|
+
}
|
|
328
|
+
switch(step.type){
|
|
329
|
+
case 'play':
|
|
330
|
+
if (step.wait === 'complete') {
|
|
331
|
+
blocked = true;
|
|
332
|
+
}
|
|
333
|
+
return {
|
|
334
|
+
...step,
|
|
335
|
+
options: {
|
|
336
|
+
...step.options,
|
|
337
|
+
sync: step.options?.sync ?? sync
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
case 'sequence':
|
|
341
|
+
{
|
|
342
|
+
const result = injectEntrySync(step.steps, sync);
|
|
343
|
+
blocked = result.blocked;
|
|
344
|
+
return {
|
|
345
|
+
...step,
|
|
346
|
+
steps: result.steps
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
case 'parallel':
|
|
350
|
+
{
|
|
351
|
+
let anyBranchBlocked = false;
|
|
352
|
+
const branches = step.steps.map((branch)=>{
|
|
353
|
+
const result = injectEntrySync([
|
|
354
|
+
branch
|
|
355
|
+
], sync);
|
|
356
|
+
anyBranchBlocked = anyBranchBlocked || result.blocked;
|
|
357
|
+
return result.steps[0];
|
|
358
|
+
});
|
|
359
|
+
blocked = anyBranchBlocked;
|
|
360
|
+
return {
|
|
361
|
+
...step,
|
|
362
|
+
steps: branches
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
case 'wait':
|
|
366
|
+
case 'waitEvent':
|
|
367
|
+
case 'waitMarker':
|
|
368
|
+
case 'waitFrame':
|
|
369
|
+
blocked = true;
|
|
370
|
+
return step;
|
|
371
|
+
default:
|
|
372
|
+
return step;
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
return {
|
|
376
|
+
steps: out,
|
|
377
|
+
blocked
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Return a copy of the timeline definition whose entry plays fade in over `duration`, so a state
|
|
382
|
+
* transition cross-fades against the previous state's fade-out.
|
|
383
|
+
*
|
|
384
|
+
* "Entry plays" are every `play` that starts before the control flow first blocks: plays before a
|
|
385
|
+
* `wait`/`waitEvent`/`waitMarker`/`waitFrame`, before a blocking `play` (`wait: 'complete'`), and
|
|
386
|
+
* every branch of a leading `parallel` (all branches start simultaneously). This covers parallel
|
|
387
|
+
* states whose branches would otherwise snap in at full weight while only the first faded.
|
|
388
|
+
*/ function withFadeIn(definition, duration) {
|
|
389
|
+
const def = Array.isArray(definition) ? {
|
|
390
|
+
steps: definition
|
|
391
|
+
} : definition;
|
|
392
|
+
return {
|
|
393
|
+
steps: injectEntryFadeIn(def.steps, duration).steps,
|
|
394
|
+
responses: def.responses
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Inject `fadeIn` into every entry play in `steps`. Returns the rewritten steps and whether the
|
|
399
|
+
* flow blocks before reaching the end (so callers stop injecting into later, non-entry steps).
|
|
400
|
+
*/ function injectEntryFadeIn(steps, duration) {
|
|
401
|
+
let blocked = false;
|
|
402
|
+
const out = steps.map((step)=>{
|
|
403
|
+
if (blocked) {
|
|
404
|
+
return step;
|
|
405
|
+
}
|
|
406
|
+
switch(step.type){
|
|
407
|
+
case 'play':
|
|
408
|
+
if (step.wait === 'complete') {
|
|
409
|
+
blocked = true;
|
|
410
|
+
}
|
|
411
|
+
return {
|
|
412
|
+
...step,
|
|
413
|
+
options: {
|
|
414
|
+
...step.options,
|
|
415
|
+
fadeIn: duration
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
case 'sequence':
|
|
419
|
+
{
|
|
420
|
+
const result = injectEntryFadeIn(step.steps, duration);
|
|
421
|
+
blocked = result.blocked;
|
|
422
|
+
return {
|
|
423
|
+
...step,
|
|
424
|
+
steps: result.steps
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
case 'parallel':
|
|
428
|
+
{
|
|
429
|
+
// Every branch starts simultaneously, so inject into all of them. The parallel join only
|
|
430
|
+
// blocks the steps that follow it if at least one branch blocks; if all branches are
|
|
431
|
+
// non-blocking they drain in the same flush, so the following steps are still entry plays.
|
|
432
|
+
let anyBranchBlocked = false;
|
|
433
|
+
const branches = step.steps.map((branch)=>{
|
|
434
|
+
const result = injectEntryFadeIn([
|
|
435
|
+
branch
|
|
436
|
+
], duration);
|
|
437
|
+
anyBranchBlocked = anyBranchBlocked || result.blocked;
|
|
438
|
+
return result.steps[0];
|
|
439
|
+
});
|
|
440
|
+
blocked = anyBranchBlocked;
|
|
441
|
+
return {
|
|
442
|
+
...step,
|
|
443
|
+
steps: branches
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
case 'wait':
|
|
447
|
+
case 'waitEvent':
|
|
448
|
+
case 'waitMarker':
|
|
449
|
+
case 'waitFrame':
|
|
450
|
+
blocked = true;
|
|
451
|
+
return step;
|
|
452
|
+
default:
|
|
453
|
+
return step;
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
return {
|
|
457
|
+
steps: out,
|
|
458
|
+
blocked
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Return a copy of the timeline definition whose play steps keep completed playbacks alive long
|
|
463
|
+
* enough to cross-fade into the automatic return state.
|
|
464
|
+
*/ function withCompletionFadeOut(definition, duration) {
|
|
465
|
+
const def = Array.isArray(definition) ? {
|
|
466
|
+
steps: definition
|
|
467
|
+
} : definition;
|
|
468
|
+
return {
|
|
469
|
+
steps: injectCompletionFadeOut(def.steps, duration),
|
|
470
|
+
responses: def.responses
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function injectCompletionFadeOut(steps, duration) {
|
|
474
|
+
return steps.map((step)=>{
|
|
475
|
+
switch(step.type){
|
|
476
|
+
case 'play':
|
|
477
|
+
return {
|
|
478
|
+
...step,
|
|
479
|
+
options: {
|
|
480
|
+
...step.options,
|
|
481
|
+
completionFadeOut: step.options?.completionFadeOut ?? duration
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
case 'sequence':
|
|
485
|
+
return {
|
|
486
|
+
...step,
|
|
487
|
+
steps: injectCompletionFadeOut(step.steps, duration)
|
|
488
|
+
};
|
|
489
|
+
case 'parallel':
|
|
490
|
+
return {
|
|
491
|
+
...step,
|
|
492
|
+
steps: step.steps.map((branch)=>injectCompletionFadeOut([
|
|
493
|
+
branch
|
|
494
|
+
], duration)[0])
|
|
495
|
+
};
|
|
496
|
+
default:
|
|
497
|
+
return step;
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export { AnimationController };
|
|
503
|
+
//# sourceMappingURL=animationcontroller.js.map
|