@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.
Files changed (48) hide show
  1. package/dist/assets/{index-DlnbO59X.js → index-ySiLtSxz.js} +2 -2
  2. package/dist/assistant/zephyr-types-index.json +14215 -7892
  3. package/dist/index.html +1 -1
  4. package/dist/modules/zephyr3d_backend-webgl.js.map +1 -1
  5. package/dist/modules/zephyr3d_backend-webgpu.js.map +1 -1
  6. package/dist/modules/zephyr3d_base.js +152 -152
  7. package/dist/modules/zephyr3d_base.js.map +1 -1
  8. package/dist/modules/zephyr3d_imgui.js.map +1 -1
  9. package/dist/modules/zephyr3d_loaders.js +6 -6
  10. package/dist/modules/zephyr3d_loaders.js.map +1 -1
  11. package/dist/modules/zephyr3d_scene.js +2538 -348
  12. package/dist/modules/zephyr3d_scene.js.map +1 -1
  13. package/dist/vendor/zephyr3d/backend-webgl/dist/index.js.map +1 -1
  14. package/dist/vendor/zephyr3d/backend-webgpu/dist/index.js.map +1 -1
  15. package/dist/vendor/zephyr3d/base/dist/vfs/common.js +152 -152
  16. package/dist/vendor/zephyr3d/base/dist/vfs/common.js.map +1 -1
  17. package/dist/vendor/zephyr3d/imgui/dist/index.d.ts +7 -0
  18. package/dist/vendor/zephyr3d/imgui/dist/index.js.map +1 -1
  19. package/dist/vendor/zephyr3d/loaders/dist/gltf/gltf_importer.js +6 -6
  20. package/dist/vendor/zephyr3d/loaders/dist/gltf/gltf_importer.js.map +1 -1
  21. package/dist/vendor/zephyr3d/scene/dist/animation/animation.js +87 -0
  22. package/dist/vendor/zephyr3d/scene/dist/animation/animation.js.map +1 -1
  23. package/dist/vendor/zephyr3d/scene/dist/animation/animationcontroller.js +503 -0
  24. package/dist/vendor/zephyr3d/scene/dist/animation/animationcontroller.js.map +1 -0
  25. package/dist/vendor/zephyr3d/scene/dist/animation/animationset.js +782 -156
  26. package/dist/vendor/zephyr3d/scene/dist/animation/animationset.js.map +1 -1
  27. package/dist/vendor/zephyr3d/scene/dist/animation/animationtimeline.js +974 -0
  28. package/dist/vendor/zephyr3d/scene/dist/animation/animationtimeline.js.map +1 -0
  29. package/dist/vendor/zephyr3d/scene/dist/animation/joint_dynamics/convex_collider.js +320 -0
  30. package/dist/vendor/zephyr3d/scene/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
  31. package/dist/vendor/zephyr3d/scene/dist/app/scriptregistry.js +130 -125
  32. package/dist/vendor/zephyr3d/scene/dist/app/scriptregistry.js.map +1 -1
  33. package/dist/vendor/zephyr3d/scene/dist/asset/model.js +64 -63
  34. package/dist/vendor/zephyr3d/scene/dist/asset/model.js.map +1 -1
  35. package/dist/vendor/zephyr3d/scene/dist/index.d.ts +1270 -9
  36. package/dist/vendor/zephyr3d/scene/dist/index.js +3 -1
  37. package/dist/vendor/zephyr3d/scene/dist/index.js.map +1 -1
  38. package/dist/vendor/zephyr3d/scene/dist/material/mixins/lit.js +7 -3
  39. package/dist/vendor/zephyr3d/scene/dist/material/mixins/lit.js.map +1 -1
  40. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/manager.js +1 -0
  41. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/manager.js.map +1 -1
  42. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/animation.js +4 -3
  43. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/animation.js.map +1 -1
  44. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/camera.js +1 -1
  45. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/camera.js.map +1 -1
  46. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/node.js +3 -2
  47. package/dist/vendor/zephyr3d/scene/dist/utility/serialization/scene/node.js.map +1 -1
  48. 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