@vvfx/sdk 0.2.8 → 0.2.10

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 (111) hide show
  1. package/dist/config.js +231 -0
  2. package/dist/exporter/config.js +18 -0
  3. package/dist/exporter/const.js +71 -0
  4. package/dist/exporter/export-media.js +878 -0
  5. package/dist/exporter/exporter.js +121 -0
  6. package/dist/exporter/index.js +1 -0
  7. package/dist/exporter/types.js +1 -0
  8. package/dist/exporter/utils.js +190 -0
  9. package/dist/exporter/wav-audio.js +50 -0
  10. package/dist/gesture-handler/gizmo/adsorption-gizmo.js +227 -0
  11. package/dist/gesture-handler/gizmo/control-gizmo.js +149 -0
  12. package/dist/gesture-handler/gizmo/gizmo.js +17 -0
  13. package/dist/gesture-handler/gizmo/icon-gizmo.js +189 -0
  14. package/dist/gesture-handler/gizmo/index.js +6 -0
  15. package/dist/gesture-handler/gizmo/item-create-gizmo.js +256 -0
  16. package/dist/gesture-handler/gizmo/loading-gizmo.js +320 -0
  17. package/dist/gesture-handler/gizmo/mask-gizmo.js +648 -0
  18. package/dist/gesture-handler/gizmo/picture-cut-gizmo.js +550 -0
  19. package/dist/gesture-handler/gizmo/picture-expand-gizmo.js +491 -0
  20. package/dist/gesture-handler/gizmo/preference-gizmo.js +148 -0
  21. package/dist/gesture-handler/gizmo/selector-gizmo.d.ts +1 -1
  22. package/dist/gesture-handler/gizmo/selector-gizmo.js +468 -0
  23. package/dist/gesture-handler/gizmo/sprite-text-edit-gizmo.js +247 -0
  24. package/dist/gesture-handler/gizmo/text-gizmo.js +1033 -0
  25. package/dist/gesture-handler/gizmo/transform-gizmo.js +1199 -0
  26. package/dist/gesture-handler/gizmo/type.js +18 -0
  27. package/dist/gesture-handler/index.js +3 -0
  28. package/dist/gesture-handler/src/gesture-handler.js +770 -0
  29. package/dist/gesture-handler/src/icons.js +24 -0
  30. package/dist/gesture-handler/utils.js +40 -0
  31. package/dist/html-overlay/anchor-navigation-runtime.js +27 -0
  32. package/dist/html-overlay/auto-height-layout.d.ts +6 -0
  33. package/dist/html-overlay/auto-height-layout.js +7 -0
  34. package/dist/html-overlay/auto-height-runtime.js +132 -0
  35. package/dist/html-overlay/document-runtime.js +71 -0
  36. package/dist/html-overlay/document-transform.js +96 -0
  37. package/dist/html-overlay/dom-order.js +8 -0
  38. package/dist/html-overlay/host-message-runtime.js +24 -0
  39. package/dist/html-overlay/html-capture-runtime.js +282 -0
  40. package/dist/html-overlay/html-viewport-style.js +10 -0
  41. package/dist/html-overlay/index.js +1 -0
  42. package/dist/html-overlay/manager.d.ts +1 -0
  43. package/dist/html-overlay/manager.js +1677 -0
  44. package/dist/html-overlay/mime-utils.js +21 -0
  45. package/dist/html-overlay/overlay-transform.js +57 -0
  46. package/dist/html-overlay/path-utils.js +35 -0
  47. package/dist/html-overlay/shell-runtime.js +12 -0
  48. package/dist/index.cjs +34 -8
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.js +13 -75311
  51. package/dist/index.mjs +75337 -0
  52. package/dist/index.mjs.map +1 -0
  53. package/dist/layer-order.js +8 -0
  54. package/dist/math/box2.js +396 -0
  55. package/dist/math/circle.js +131 -0
  56. package/dist/math/euler.js +35 -0
  57. package/dist/math/index.js +13 -0
  58. package/dist/math/line2.js +153 -0
  59. package/dist/math/line3.js +36 -0
  60. package/dist/math/matrix4.js +207 -0
  61. package/dist/math/plane.js +52 -0
  62. package/dist/math/quaternion.js +3 -0
  63. package/dist/math/ray-caster.js +25 -0
  64. package/dist/math/ray.js +47 -0
  65. package/dist/math/type.js +1 -0
  66. package/dist/math/utils.js +77 -0
  67. package/dist/math/vector2.js +70 -0
  68. package/dist/math/vector3.js +60 -0
  69. package/dist/screen-shot/index.js +1 -0
  70. package/dist/screen-shot/screen-shot.js +113 -0
  71. package/dist/sdk-item/base-item.js +160 -0
  72. package/dist/sdk-item/card-item.js +131 -0
  73. package/dist/sdk-item/effects-item.js +144 -0
  74. package/dist/sdk-item/frame-item.js +217 -0
  75. package/dist/sdk-item/generator-item.js +188 -0
  76. package/dist/sdk-item/group-item.js +116 -0
  77. package/dist/sdk-item/index.js +39 -0
  78. package/dist/sdk-item/sprite-item.js +147 -0
  79. package/dist/sdk-item/text-item.js +223 -0
  80. package/dist/sdk-item/types.d.ts +1 -1
  81. package/dist/sdk-item/types.js +15 -0
  82. package/dist/sdk-item/video-item.js +174 -0
  83. package/dist/sdk.js +1908 -0
  84. package/dist/service/UndoRedo.js +79 -0
  85. package/dist/shared/index.d.ts +1 -0
  86. package/dist/shared/index.js +2 -0
  87. package/dist/shared/player.d.ts +1 -1
  88. package/dist/shared/player.js +9 -0
  89. package/dist/shared/spec.d.ts +1 -0
  90. package/dist/shared/spec.js +1 -0
  91. package/dist/size-adapte/index.js +1 -0
  92. package/dist/size-adapte/size-adapt.js +395 -0
  93. package/dist/types.d.ts +5 -1
  94. package/dist/types.js +17 -0
  95. package/dist/utils/background-manager.js +189 -0
  96. package/dist/utils/common-utils.js +183 -0
  97. package/dist/utils/index.js +8 -0
  98. package/dist/utils/interaction-utils.js +205 -0
  99. package/dist/utils/json-data-utils.js +1529 -0
  100. package/dist/utils/layout-utils.js +1217 -0
  101. package/dist/utils/page-data-utils.js +5397 -0
  102. package/dist/utils/player-data-utils.js +48 -0
  103. package/dist/utils/types.js +8 -0
  104. package/dist/wireframe/common/box.js +18 -0
  105. package/dist/wireframe/common/dashed-line.js +21 -0
  106. package/dist/wireframe/common/line.js +17 -0
  107. package/dist/wireframe/common/pixi-ext.js +1 -0
  108. package/dist/wireframe/common/pixi.js +31 -0
  109. package/dist/wireframe/index.js +4 -0
  110. package/package.json +10 -3
  111. package/dist/index.js.map +0 -1
@@ -0,0 +1,1529 @@
1
+ import { Vector2, Vector3 } from '../math';
2
+ import { spec, generateGUID, PlayerVersion } from '../shared';
3
+ /**
4
+ * @description 创建 Frame 元素的内部合成场景
5
+ * @param frameId Frame 元素ID(用于标识)
6
+ * @param size 场景尺寸 [width, height]
7
+ * @param name 场景名称
8
+ * @returns Frame 内部的合成场景
9
+ */
10
+ export function createFrameCompositionScene(frameId, size, name) {
11
+ const compositionId = generateGUID();
12
+ const timelineAssetId = generateGUID();
13
+ const compositionComponentId = generateGUID();
14
+ const timelineAsset = {
15
+ id: timelineAssetId,
16
+ dataType: spec.DataType.TimelineAsset,
17
+ tracks: [],
18
+ };
19
+ const compositionComponent = {
20
+ id: compositionComponentId,
21
+ item: { id: compositionId },
22
+ dataType: spec.DataType.CompositionComponent,
23
+ items: [],
24
+ timelineAsset: { id: timelineAssetId },
25
+ sceneBindings: [],
26
+ };
27
+ const [width, height] = size;
28
+ const composition = {
29
+ id: compositionId,
30
+ name: name + '_inner',
31
+ duration: 999,
32
+ startTime: 0,
33
+ endBehavior: spec.EndBehavior.forward,
34
+ previewSize: [...size],
35
+ camera: {
36
+ fov: 60,
37
+ far: 40,
38
+ near: 0.1,
39
+ clipMode: 1,
40
+ position: [0, 0, 8],
41
+ rotation: [0, 0, 0],
42
+ // @ts-expect-error: aspect 属性在 CameraOptions 上有定义但类型声明不完善
43
+ aspect: width / height,
44
+ },
45
+ components: [{ id: compositionComponentId }],
46
+ };
47
+ const scene = {
48
+ version: spec.JSONSceneVersion['3_6'],
49
+ playerVersion: {
50
+ web: PlayerVersion,
51
+ native: '10.7.6',
52
+ },
53
+ type: 'ge',
54
+ compositionId: compositionId,
55
+ compositions: [composition],
56
+ images: [],
57
+ plugins: [],
58
+ bins: [],
59
+ textures: [],
60
+ items: [],
61
+ components: [compositionComponent],
62
+ materials: [],
63
+ shaders: [],
64
+ geometries: [],
65
+ animations: [],
66
+ miscs: [timelineAsset],
67
+ };
68
+ return scene;
69
+ }
70
+ /**
71
+ * @description 创建基础场景信息
72
+ * @param viewSize 画布大小
73
+ * @returns 场景信息
74
+ */
75
+ export function getBasicScene(viewSize = [750, 750], name = 'AI 设计师画布') {
76
+ const compositionId = generateGUID();
77
+ const timelineAsset = {
78
+ id: generateGUID(),
79
+ dataType: spec.DataType.TimelineAsset,
80
+ tracks: [],
81
+ };
82
+ const basicCompositionComponent = {
83
+ id: generateGUID(),
84
+ item: { id: compositionId },
85
+ dataType: spec.DataType.CompositionComponent,
86
+ items: [],
87
+ timelineAsset: { id: timelineAsset.id },
88
+ sceneBindings: [],
89
+ };
90
+ const basicComposition = {
91
+ id: compositionId,
92
+ name,
93
+ duration: 999,
94
+ startTime: 0,
95
+ endBehavior: spec.EndBehavior.forward,
96
+ previewSize: [...viewSize],
97
+ camera: {
98
+ fov: 60,
99
+ far: 40,
100
+ near: 0.1,
101
+ clipMode: 1,
102
+ position: [0, 0, 8],
103
+ rotation: [0, 0, 0],
104
+ //@ts-expect-error @赤芍 to fix4
105
+ aspect: viewSize[0] / viewSize[1]
106
+ },
107
+ components: [{ id: basicCompositionComponent.id }],
108
+ };
109
+ const scene = {
110
+ version: spec.JSONSceneVersion['3_6'],
111
+ playerVersion: {
112
+ web: PlayerVersion,
113
+ native: '10.7.6'
114
+ },
115
+ type: 'ge',
116
+ compositionId: compositionId,
117
+ compositions: [],
118
+ images: [],
119
+ plugins: [],
120
+ bins: [],
121
+ textures: [],
122
+ items: [],
123
+ components: [],
124
+ materials: [],
125
+ shaders: [],
126
+ geometries: [],
127
+ animations: [],
128
+ miscs: [],
129
+ };
130
+ scene.miscs.push(timelineAsset);
131
+ scene.compositions.push(basicComposition);
132
+ scene.components.push(basicCompositionComponent);
133
+ return scene;
134
+ }
135
+ export function createSpriteItemJSON(spriteId, position, rotation, size, scale, name, texture) {
136
+ const componentId = generateGUID();
137
+ const spriteItem = {
138
+ id: spriteId,
139
+ name,
140
+ dataType: spec.DataType.VFXItemData,
141
+ components: [{ id: componentId }],
142
+ delay: 0,
143
+ duration: 999,
144
+ endBehavior: spec.EndBehavior.restart,
145
+ renderLevel: spec.RenderLevel.BPlus,
146
+ type: spec.ItemType.sprite,
147
+ visible: true,
148
+ transform: {
149
+ position: { x: position.x, y: position.y, z: position.z },
150
+ eulerHint: { x: rotation.x, y: rotation.y, z: rotation.z },
151
+ size: { x: size.x, y: size.y },
152
+ scale: { x: scale.x, y: scale.y, z: scale.z },
153
+ anchor: { x: 0, y: 0 }
154
+ },
155
+ content: undefined,
156
+ };
157
+ const component = {
158
+ id: componentId,
159
+ item: { id: spriteId },
160
+ dataType: spec.DataType.SpriteComponent,
161
+ options: {
162
+ startColor: [1, 1, 1, 1],
163
+ },
164
+ renderer: {
165
+ renderMode: spec.RenderMode.MESH,
166
+ },
167
+ };
168
+ if (texture) {
169
+ component.renderer.texture = { id: texture };
170
+ }
171
+ const activationPlayableAsset = {
172
+ id: generateGUID(),
173
+ dataType: spec.DataType.ActivationPlayableAsset,
174
+ };
175
+ const activationTrack = {
176
+ id: generateGUID(),
177
+ dataType: spec.DataType.ActivationTrack,
178
+ children: [],
179
+ clips: [{
180
+ start: 0,
181
+ duration: 999,
182
+ endBehavior: spec.EndBehavior.restart,
183
+ asset: {
184
+ id: activationPlayableAsset.id,
185
+ },
186
+ }],
187
+ };
188
+ const spriteColorPlayableAsset = {
189
+ id: generateGUID(),
190
+ dataType: spec.DataType.SpriteColorPlayableAsset,
191
+ startColor: [1, 1, 1, 1],
192
+ };
193
+ const spriteColorTrack = {
194
+ id: generateGUID(),
195
+ dataType: spec.DataType.SpriteColorTrack,
196
+ children: [],
197
+ clips: [{
198
+ start: 0,
199
+ duration: 999,
200
+ endBehavior: spec.EndBehavior.restart,
201
+ asset: {
202
+ id: spriteColorPlayableAsset.id,
203
+ },
204
+ }]
205
+ };
206
+ const tracks = [activationTrack, spriteColorTrack];
207
+ const playableAssets = [activationPlayableAsset, spriteColorPlayableAsset];
208
+ return {
209
+ item: spriteItem,
210
+ components: [component],
211
+ tracks,
212
+ playableAssets,
213
+ };
214
+ }
215
+ /**
216
+ * @description 创建视频生成器 Item JSON
217
+ * @description 以 SpriteItem 形式渲染,但设置透明颜色
218
+ * @description 在 content 中存储 isVideoGenerator 标记
219
+ */
220
+ export function createVideoGeneratorItemJSON(id, position, rotation, size, scale, name) {
221
+ const componentId = generateGUID();
222
+ const vfxItem = {
223
+ id,
224
+ name,
225
+ dataType: spec.DataType.VFXItemData,
226
+ components: [{ id: componentId }],
227
+ delay: 0,
228
+ duration: 999,
229
+ endBehavior: spec.EndBehavior.restart,
230
+ renderLevel: spec.RenderLevel.BPlus,
231
+ type: spec.ItemType.sprite,
232
+ visible: true,
233
+ transform: {
234
+ position: { x: position.x, y: position.y, z: position.z },
235
+ eulerHint: { x: rotation.x, y: rotation.y, z: rotation.z },
236
+ size: { x: size.x, y: size.y },
237
+ scale: { x: scale.x, y: scale.y, z: scale.z },
238
+ anchor: { x: 0, y: 0 }
239
+ },
240
+ // 存储视频生成器标记
241
+ content: {
242
+ isVideoGenerator: true
243
+ },
244
+ };
245
+ // 使用透明颜色 [1, 1, 1, 0]
246
+ const component = {
247
+ id: componentId,
248
+ item: { id },
249
+ dataType: spec.DataType.SpriteComponent,
250
+ options: {
251
+ startColor: [1, 1, 1, 0], // 完全透明
252
+ },
253
+ renderer: {
254
+ renderMode: spec.RenderMode.MESH,
255
+ },
256
+ };
257
+ const activationPlayableAsset = {
258
+ id: generateGUID(),
259
+ dataType: spec.DataType.ActivationPlayableAsset,
260
+ };
261
+ const activationTrack = {
262
+ id: generateGUID(),
263
+ dataType: spec.DataType.ActivationTrack,
264
+ children: [],
265
+ clips: [{
266
+ start: 0,
267
+ duration: 999,
268
+ endBehavior: spec.EndBehavior.restart,
269
+ asset: {
270
+ id: activationPlayableAsset.id,
271
+ },
272
+ }],
273
+ };
274
+ // 透明颜色 playable asset
275
+ const spriteColorPlayableAsset = {
276
+ id: generateGUID(),
277
+ dataType: spec.DataType.SpriteColorPlayableAsset,
278
+ startColor: [1, 1, 1, 0], // 完全透明
279
+ };
280
+ const spriteColorTrack = {
281
+ id: generateGUID(),
282
+ dataType: spec.DataType.SpriteColorTrack,
283
+ children: [],
284
+ clips: [{
285
+ start: 0,
286
+ duration: 999,
287
+ endBehavior: spec.EndBehavior.restart,
288
+ asset: {
289
+ id: spriteColorPlayableAsset.id,
290
+ },
291
+ }],
292
+ };
293
+ const tracks = [activationTrack, spriteColorTrack];
294
+ const playableAssets = [activationPlayableAsset, spriteColorPlayableAsset];
295
+ return {
296
+ item: vfxItem,
297
+ components: [component],
298
+ tracks,
299
+ playableAssets,
300
+ };
301
+ }
302
+ export function createNullItemJSON(nullId, name, position, scale = new Vector2(1, 1), rotation = new Vector3(), worldSize = new Vector2(1, 1)) {
303
+ const positionInfo = JSON.parse(JSON.stringify(position));
304
+ const nullItem = {
305
+ id: nullId,
306
+ name: name,
307
+ duration: 999,
308
+ type: spec.ItemType.null,
309
+ visible: true,
310
+ endBehavior: spec.EndBehavior.restart,
311
+ delay: 0,
312
+ renderLevel: spec.RenderLevel.BPlus,
313
+ content: {
314
+ options: {
315
+ startColor: [1, 1, 1, 1],
316
+ }
317
+ },
318
+ components: [],
319
+ transform: {
320
+ position: positionInfo,
321
+ eulerHint: { x: rotation.x, y: rotation.y, z: rotation.z },
322
+ size: JSON.parse(JSON.stringify(worldSize)),
323
+ scale: { x: scale.x, y: scale.y, z: 1 },
324
+ },
325
+ dataType: spec.DataType.VFXItemData,
326
+ };
327
+ const activationPlayableAsset = {
328
+ id: generateGUID(),
329
+ dataType: spec.DataType.ActivationPlayableAsset,
330
+ };
331
+ const activationTrack = {
332
+ id: generateGUID(),
333
+ dataType: spec.DataType.ActivationTrack,
334
+ children: [],
335
+ clips: [{
336
+ start: 0,
337
+ duration: 999,
338
+ endBehavior: spec.EndBehavior.restart,
339
+ asset: {
340
+ id: activationPlayableAsset.id,
341
+ },
342
+ }],
343
+ };
344
+ const tracks = [activationTrack];
345
+ const playableAssets = [activationPlayableAsset];
346
+ return {
347
+ item: nullItem,
348
+ components: [],
349
+ tracks,
350
+ playableAssets,
351
+ };
352
+ }
353
+ export function createTextItemJSON(textId, name, position, rotation, size, fontInfo) {
354
+ const componentId = generateGUID();
355
+ const textItem = {
356
+ id: textId,
357
+ name,
358
+ dataType: spec.DataType.VFXItemData,
359
+ components: [{ id: componentId }],
360
+ delay: 0,
361
+ duration: 999,
362
+ endBehavior: spec.EndBehavior.restart,
363
+ renderLevel: spec.RenderLevel.BPlus,
364
+ type: spec.ItemType.text,
365
+ visible: true,
366
+ transform: {
367
+ position: { x: position.x, y: position.y, z: position.z },
368
+ eulerHint: { x: rotation.x, y: rotation.y, z: rotation.z },
369
+ scale: { x: size.x, y: size.y, z: 1 },
370
+ anchor: { x: 0, y: 0 }
371
+ },
372
+ content: undefined,
373
+ };
374
+ const component = {
375
+ id: componentId,
376
+ item: { id: textId },
377
+ dataType: spec.DataType.TextComponent,
378
+ options: fontInfo,
379
+ renderer: { renderMode: 1 }
380
+ };
381
+ const activationPlayableAsset = {
382
+ id: generateGUID(),
383
+ dataType: spec.DataType.ActivationPlayableAsset,
384
+ };
385
+ const activationTrack = {
386
+ id: generateGUID(),
387
+ dataType: spec.DataType.ActivationTrack,
388
+ children: [],
389
+ clips: [{
390
+ start: 0,
391
+ duration: 999,
392
+ endBehavior: spec.EndBehavior.restart,
393
+ asset: {
394
+ id: activationPlayableAsset.id,
395
+ },
396
+ }],
397
+ };
398
+ const tracks = [activationTrack];
399
+ const playableAssets = [activationPlayableAsset];
400
+ return {
401
+ item: textItem,
402
+ components: [component],
403
+ tracks,
404
+ playableAssets,
405
+ };
406
+ }
407
+ export function createVideoItemJSON(videoId, position, rotation, size, scale, name, isMuted = false, transparent = false, volume = 1, playbackRate = 1, texture, video) {
408
+ const componentId = generateGUID();
409
+ const videoItem = {
410
+ id: videoId,
411
+ name,
412
+ dataType: spec.DataType.VFXItemData,
413
+ components: [{ id: componentId }],
414
+ delay: 0,
415
+ duration: 999,
416
+ endBehavior: spec.EndBehavior.restart,
417
+ renderLevel: spec.RenderLevel.BPlus,
418
+ type: spec.ItemType.video,
419
+ visible: true,
420
+ transform: {
421
+ position: { x: position.x, y: position.y, z: position.z },
422
+ eulerHint: { x: rotation.x, y: rotation.y, z: rotation.z },
423
+ size: { x: size.x, y: size.y },
424
+ scale: { x: scale.x, y: scale.y, z: scale.z },
425
+ anchor: { x: 0, y: 0 }
426
+ },
427
+ content: undefined,
428
+ };
429
+ const component = {
430
+ id: componentId,
431
+ item: { id: videoId },
432
+ dataType: spec.DataType.VideoComponent,
433
+ options: {
434
+ startColor: [1, 1, 1, 1],
435
+ muted: isMuted,
436
+ video: {
437
+ id: video
438
+ },
439
+ volume,
440
+ playbackRate,
441
+ transparent
442
+ },
443
+ renderer: {
444
+ renderMode: spec.RenderMode.MESH,
445
+ },
446
+ };
447
+ if (texture) {
448
+ component.renderer.texture = { id: texture };
449
+ }
450
+ const activationPlayableAsset = {
451
+ id: generateGUID(),
452
+ dataType: spec.DataType.ActivationPlayableAsset,
453
+ };
454
+ const activationTrack = {
455
+ id: generateGUID(),
456
+ dataType: spec.DataType.ActivationTrack,
457
+ children: [],
458
+ clips: [{
459
+ start: 0,
460
+ duration: 999,
461
+ endBehavior: spec.EndBehavior.restart,
462
+ asset: {
463
+ id: activationPlayableAsset.id,
464
+ },
465
+ }],
466
+ };
467
+ const spriteColorPlayableAsset = {
468
+ id: generateGUID(),
469
+ dataType: spec.DataType.SpriteColorPlayableAsset,
470
+ startColor: [1, 1, 1, 1],
471
+ };
472
+ const spriteColorTrack = {
473
+ id: generateGUID(),
474
+ dataType: spec.DataType.SpriteColorTrack,
475
+ children: [],
476
+ clips: [{
477
+ start: 0,
478
+ duration: 999,
479
+ endBehavior: spec.EndBehavior.restart,
480
+ asset: {
481
+ id: spriteColorPlayableAsset.id,
482
+ },
483
+ }]
484
+ };
485
+ const tracks = [activationTrack, spriteColorTrack];
486
+ const playableAssets = [activationPlayableAsset, spriteColorPlayableAsset];
487
+ return {
488
+ item: videoItem,
489
+ components: [component],
490
+ tracks,
491
+ playableAssets,
492
+ };
493
+ }
494
+ /**
495
+ * @description 创建预合成元素
496
+ * @param effectsId 预合成元素ID
497
+ * @param position 位置信息
498
+ * @param rotation 旋转信息
499
+ * @param scale 缩放信息
500
+ * @param name 名称
501
+ */
502
+ export function createCompositionItemJSON(effectsId, position, rotation, scale, name) {
503
+ const positionInfo = JSON.parse(JSON.stringify(position));
504
+ const compositionItem = {
505
+ id: effectsId,
506
+ name: name,
507
+ duration: 999,
508
+ type: spec.ItemType.null,
509
+ visible: true,
510
+ endBehavior: spec.EndBehavior.restart,
511
+ delay: 0,
512
+ renderLevel: spec.RenderLevel.BPlus,
513
+ content: {
514
+ options: {
515
+ startColor: [1, 1, 1, 1],
516
+ }
517
+ },
518
+ components: [],
519
+ transform: {
520
+ position: positionInfo,
521
+ eulerHint: { x: rotation.x, y: rotation.y, z: rotation.z },
522
+ size: { x: 1, y: 1 },
523
+ scale: { x: scale.x, y: scale.y, z: 1 },
524
+ },
525
+ dataType: spec.DataType.VFXItemData,
526
+ };
527
+ const activationPlayableAsset = {
528
+ id: generateGUID(),
529
+ dataType: spec.DataType.ActivationPlayableAsset,
530
+ };
531
+ const activationTrack = {
532
+ id: generateGUID(),
533
+ dataType: spec.DataType.ActivationTrack,
534
+ children: [],
535
+ clips: [{
536
+ start: 0,
537
+ duration: 999,
538
+ endBehavior: spec.EndBehavior.restart,
539
+ asset: {
540
+ id: activationPlayableAsset.id,
541
+ },
542
+ }],
543
+ };
544
+ const tracks = [activationTrack];
545
+ const playableAssets = [activationPlayableAsset];
546
+ return {
547
+ item: compositionItem,
548
+ components: [],
549
+ tracks,
550
+ playableAssets,
551
+ };
552
+ }
553
+ /**
554
+ * @description 添加元素信息到JSON
555
+ * @param scene 场景信息
556
+ * @param itemInfo 元素信息
557
+ */
558
+ export function addItemInfoToScene(scene, itemInfo) {
559
+ scene.items.push(itemInfo.item);
560
+ scene.miscs.push(...itemInfo.tracks, ...itemInfo.playableAssets);
561
+ scene.components.push(...itemInfo.components);
562
+ const mainComposition = scene.compositions.find(c => c.id === scene.compositionId) ?? scene.compositions[0];
563
+ const mainCompositionComponent = scene.components.find(c => c.id === mainComposition.components[0]?.id);
564
+ if (mainCompositionComponent) {
565
+ mainCompositionComponent.items.push({ id: itemInfo.item.id });
566
+ const bindingTrackData = {
567
+ id: generateGUID(),
568
+ dataType: spec.DataType.ObjectBindingTrack,
569
+ children: [],
570
+ clips: [],
571
+ };
572
+ for (const track of itemInfo.tracks) {
573
+ bindingTrackData.children.push({ id: track.id });
574
+ }
575
+ const timelineAsset = scene.miscs.find(t => t.id === mainCompositionComponent.timelineAsset.id);
576
+ timelineAsset.tracks.push({ id: bindingTrackData.id });
577
+ mainCompositionComponent.sceneBindings.push({
578
+ key: { id: bindingTrackData.id },
579
+ value: { id: itemInfo.item.id },
580
+ });
581
+ scene.miscs.push(bindingTrackData);
582
+ }
583
+ }
584
+ /**
585
+ * @description 设置元素父节点信息
586
+ * @param scene 场景信息
587
+ * @param itemId 元素Id
588
+ * @param parentId 父节点Id
589
+ */
590
+ export function setItemJSONParentId(scene, itemId, parentId) {
591
+ const targetItemIndex = scene.items.findIndex(item => item.id === itemId);
592
+ if (targetItemIndex < 0) {
593
+ return;
594
+ }
595
+ clearItemParentInfo(scene, itemId);
596
+ const targetParentItem = scene.items.find(item => item.id === parentId);
597
+ if (!targetParentItem) {
598
+ return;
599
+ }
600
+ scene.items[targetItemIndex].parentId = parentId;
601
+ const { position, eulerHint, scale } = targetParentItem.transform;
602
+ scene.items[targetItemIndex].transform ??= {
603
+ position: { x: 0, y: 0, z: 0 },
604
+ eulerHint: { x: 0, y: 0, z: 0 },
605
+ scale: { x: 1, y: 1, z: 1 },
606
+ size: { x: 1, y: 1 },
607
+ };
608
+ scene.items[targetItemIndex].transform.position.x -= position.x;
609
+ scene.items[targetItemIndex].transform.position.y -= position.y;
610
+ scene.items[targetItemIndex].transform.position.z -= position.z;
611
+ scene.items[targetItemIndex].transform.eulerHint.x -= eulerHint.x;
612
+ scene.items[targetItemIndex].transform.eulerHint.y -= eulerHint.y;
613
+ scene.items[targetItemIndex].transform.eulerHint.z -= eulerHint.z;
614
+ scene.items[targetItemIndex].transform.scale.x /= (scale.x === 0 ? 1 : scale.x);
615
+ scene.items[targetItemIndex].transform.scale.y /= (scale.y === 0 ? 1 : scale.y);
616
+ scene.items[targetItemIndex].transform.scale.z /= (scale.z === 0 ? 1 : scale.z);
617
+ }
618
+ /**
619
+ * @description 清理元素父节点信息
620
+ * @param scene 场景信息
621
+ * @param itemId 元素Id
622
+ */
623
+ export function clearItemParentInfo(scene, itemId) {
624
+ const targetItemIndex = scene.items.findIndex(item => item.id === itemId);
625
+ if (!scene.items[targetItemIndex]?.parentId) {
626
+ return;
627
+ }
628
+ const targetParentItem = scene.items.find(item => item.id === scene.items[targetItemIndex].parentId);
629
+ if (!targetParentItem) {
630
+ delete scene.items[targetItemIndex].parentId;
631
+ return;
632
+ }
633
+ const { position, eulerHint, scale } = targetParentItem.transform;
634
+ scene.items[targetItemIndex].transform ??= {
635
+ position: { x: 0, y: 0, z: 0 },
636
+ eulerHint: { x: 0, y: 0, z: 0 },
637
+ scale: { x: 1, y: 1, z: 1 },
638
+ size: { x: 1, y: 1 },
639
+ };
640
+ scene.items[targetItemIndex].transform.position.x += position.x;
641
+ scene.items[targetItemIndex].transform.position.y += position.y;
642
+ scene.items[targetItemIndex].transform.position.z += position.z;
643
+ scene.items[targetItemIndex].transform.eulerHint.x += eulerHint.x;
644
+ scene.items[targetItemIndex].transform.eulerHint.y += eulerHint.y;
645
+ scene.items[targetItemIndex].transform.eulerHint.z += eulerHint.z;
646
+ scene.items[targetItemIndex].transform.scale.x *= (scale.x === 0 ? 1 : scale.x);
647
+ scene.items[targetItemIndex].transform.scale.y *= (scale.y === 0 ? 1 : scale.y);
648
+ scene.items[targetItemIndex].transform.scale.z *= (scale.z === 0 ? 1 : scale.z);
649
+ }
650
+ /**
651
+ * @description 删除元素信息
652
+ * @param scene 场景信息
653
+ * @param itemId 元素Id
654
+ */
655
+ export function deleteItemInfoFromScene(scene, itemId) {
656
+ const item = scene.items.find(item => item.id === itemId);
657
+ if (!item) {
658
+ console.warn(`item ${itemId} is not existed.`);
659
+ return;
660
+ }
661
+ // 删除 元素 信息
662
+ scene.items = scene.items.filter(item => item.id !== itemId);
663
+ // 刷新 父子级 元素信息
664
+ scene.items.forEach(child => {
665
+ if (child.parentId === itemId) {
666
+ child.transform ??= {
667
+ position: { x: 0, y: 0, z: 0 },
668
+ eulerHint: { x: 0, y: 0, z: 0 },
669
+ scale: { x: 1, y: 1, z: 1 },
670
+ size: { x: 1, y: 1 },
671
+ };
672
+ child.transform.position.x += item.transform.position.x;
673
+ child.transform.position.y += item.transform.position.y;
674
+ child.transform.position.z += item.transform.position.z;
675
+ child.transform.eulerHint.x += item.transform.eulerHint.x;
676
+ child.transform.eulerHint.y += item.transform.eulerHint.y;
677
+ child.transform.eulerHint.z += item.transform.eulerHint.z;
678
+ child.transform.scale.x *= (item.transform.scale.x !== 0) ? item.transform.scale.x : 1;
679
+ child.transform.scale.y *= (item.transform.scale.y !== 0) ? item.transform.scale.y : 1;
680
+ child.transform.scale.z *= (item.transform.scale.z !== 0) ? item.transform.scale.z : 1;
681
+ }
682
+ delete child.parentId;
683
+ });
684
+ // 删除 Component 信息
685
+ const targetComponents = scene.components.filter(c => c.item.id === itemId);
686
+ scene.components = scene.components.filter(c => !(targetComponents.map(component => component.id).includes(c.id)));
687
+ targetComponents.forEach(component => {
688
+ switch (component.dataType) {
689
+ case spec.DataType.SpriteComponent: {
690
+ // 删除 Texture 信息
691
+ if (component.renderer.texture) {
692
+ const textureHasOtherUsed = scene.components.find(c => c.renderer?.texture?.id === component.renderer.texture.id);
693
+ if (!textureHasOtherUsed) {
694
+ const targetTexture = scene.textures?.find(t => t.id === component.renderer.texture?.id);
695
+ if (targetTexture) {
696
+ scene.textures = scene.textures?.filter(t => t.id !== targetTexture.id);
697
+ scene.images = scene.images.filter(image => image.id !== targetTexture.source.id);
698
+ }
699
+ }
700
+ }
701
+ break;
702
+ }
703
+ case spec.DataType.VideoComponent: {
704
+ if (component.renderer.texture) {
705
+ const textureHasOtherUsed = scene.components.find(c => c.renderer?.texture?.id === component.renderer.texture.id);
706
+ if (!textureHasOtherUsed) {
707
+ const targetTexture = scene.textures?.find(t => t.id === component.renderer.texture?.id);
708
+ if (targetTexture) {
709
+ scene.textures = scene.textures?.filter(t => t.id !== targetTexture.id);
710
+ scene.videos = scene.videos?.filter(video => video.id !== targetTexture.source.id);
711
+ }
712
+ }
713
+ }
714
+ }
715
+ }
716
+ });
717
+ // 删除 SceneBinding 信息
718
+ const toDeleteMiscIds = [];
719
+ let objectTrackId;
720
+ let timelineAssetId;
721
+ if ((+scene.version) >= 3.3) {
722
+ const compositionComponents = scene.components.filter(c => c.dataType === spec.DataType.CompositionComponent);
723
+ const compositionComponent = compositionComponents.find(c => c.items.find(item => item.id === itemId));
724
+ if (compositionComponent) {
725
+ compositionComponent.items = compositionComponent.items.filter(i => i.id !== itemId);
726
+ timelineAssetId = compositionComponent.timelineAsset.id;
727
+ objectTrackId = compositionComponent.sceneBindings.find(binding => binding.value.id === itemId)?.key.id;
728
+ compositionComponent.sceneBindings = compositionComponent.sceneBindings.filter(binding => binding.value.id !== itemId);
729
+ if (objectTrackId) {
730
+ toDeleteMiscIds.push(objectTrackId);
731
+ }
732
+ }
733
+ }
734
+ else {
735
+ const targetComposition = scene.compositions.find((c) => c.items.find((item) => item.id === itemId));
736
+ if (targetComposition) {
737
+ targetComposition.items = targetComposition.items.filter((i) => i.id !== itemId);
738
+ timelineAssetId = targetComposition.timelineAsset.id;
739
+ objectTrackId = targetComposition.sceneBindings.find((binding) => binding.value.id === itemId)?.key.id;
740
+ targetComposition.sceneBindings = targetComposition.sceneBindings.filter((binding) => binding.value.id !== itemId);
741
+ if (objectTrackId) {
742
+ toDeleteMiscIds.push(objectTrackId);
743
+ }
744
+ }
745
+ }
746
+ // 删除 miscs 信息
747
+ const timelineAssetIndex = scene.miscs.findIndex(misc => misc.id === timelineAssetId);
748
+ if (timelineAssetIndex >= 0) {
749
+ scene.miscs[timelineAssetIndex].tracks = scene.miscs[timelineAssetIndex].tracks.filter((track) => track.id !== objectTrackId);
750
+ }
751
+ const objectTrack = scene.miscs.find(misc => misc.id === objectTrackId);
752
+ if (objectTrack) {
753
+ objectTrack.children.forEach((child) => {
754
+ toDeleteMiscIds.push(child.id);
755
+ const targetTrack = scene.miscs.find(misc => misc.id === child.id);
756
+ if (targetTrack) {
757
+ toDeleteMiscIds.push(...targetTrack.clips.map((clip) => clip.asset.id));
758
+ }
759
+ });
760
+ toDeleteMiscIds.push(...objectTrack.children.map((c) => c.id));
761
+ }
762
+ scene.miscs = scene.miscs.filter(misc => !toDeleteMiscIds.includes(misc.id));
763
+ return scene;
764
+ }
765
+ /**
766
+ * @description 从场景中移除并获取元素的完整 ItemJSONInfo
767
+ * @param scene 场景信息
768
+ * @param itemId 元素ID
769
+ * @returns ItemJSONInfo 或 undefined(如果元素不存在)
770
+ */
771
+ export function removeItemInfoFromScene(scene, itemId) {
772
+ const item = scene.items.find(item => item.id === itemId);
773
+ if (!item) {
774
+ return undefined;
775
+ }
776
+ // 1. 提取 item
777
+ const itemData = { ...item };
778
+ // 2. 提取 components(包括关联的 texture 等资源)
779
+ const components = scene.components.filter(c => c.item.id === itemId);
780
+ // 3. 查找 compositionComponent
781
+ const compositionComponents = scene.components.filter(c => c.dataType === spec.DataType.CompositionComponent);
782
+ const compositionComponent = compositionComponents.find(c => c.items.find((i) => i.id === itemId));
783
+ // 4. 提取 tracks 和 playableAssets
784
+ const tracks = [];
785
+ const playableAssets = [];
786
+ if (compositionComponent) {
787
+ // 查找 sceneBindings
788
+ const sceneBinding = compositionComponent.sceneBindings.find(binding => binding.value.id === itemId);
789
+ if (sceneBinding) {
790
+ const objectTrackId = sceneBinding.key.id;
791
+ const objectTrack = scene.miscs.find(misc => misc.id === objectTrackId);
792
+ if (objectTrack) {
793
+ // 提取子 tracks
794
+ const childTrackIds = (objectTrack.children || []).map((child) => child.id);
795
+ childTrackIds.forEach(trackId => {
796
+ const track = scene.miscs.find(misc => misc.id === trackId);
797
+ if (track) {
798
+ tracks.push({ ...track });
799
+ // 提取 track 中的 playableAssets
800
+ const trackClips = track.clips;
801
+ if (trackClips) {
802
+ trackClips.forEach((clip) => {
803
+ const asset = scene.miscs.find(misc => misc.id === clip.asset.id);
804
+ if (asset) {
805
+ playableAssets.push({ ...asset });
806
+ }
807
+ });
808
+ }
809
+ }
810
+ });
811
+ // 添加 objectBindingTrack 本身到 tracks
812
+ tracks.push({ ...objectTrack });
813
+ // objectBindingTrack clips 中的 asset 也需要提取
814
+ const objectTrackClips = objectTrack.clips;
815
+ if (objectTrackClips) {
816
+ objectTrackClips.forEach((clip) => {
817
+ const asset = scene.miscs.find(misc => misc.id === clip.asset.id);
818
+ if (asset) {
819
+ playableAssets.push({ ...asset });
820
+ }
821
+ });
822
+ }
823
+ }
824
+ }
825
+ }
826
+ // 5. 从 scene 中移除数据(使用已有的 deleteItemInfoFromScene 函数)
827
+ deleteItemInfoFromScene(scene, itemId);
828
+ return {
829
+ item: itemData,
830
+ components: components.map(c => ({ ...c })),
831
+ tracks,
832
+ playableAssets,
833
+ };
834
+ }
835
+ export function getTextureUrlById(scene, itemId) {
836
+ let textureUrl;
837
+ scene.components.forEach(component => {
838
+ if (component.item.id === itemId) {
839
+ if (component.dataType === spec.DataType.SpriteComponent && component.renderer.texture) {
840
+ const targetTexture = scene.textures?.find(t => t.id === component.renderer.texture?.id);
841
+ if (targetTexture) {
842
+ textureUrl = scene.images.find(image => image.id === targetTexture.source.id)?.url;
843
+ }
844
+ }
845
+ else if (component.dataType === spec.DataType.VideoComponent && component.renderer.texture) {
846
+ const targetTexture = scene.textures?.find(t => t.id === component.renderer.texture?.id);
847
+ if (targetTexture) {
848
+ textureUrl = scene.videos?.find(video => video.id === targetTexture.source.id)?.url;
849
+ }
850
+ }
851
+ }
852
+ });
853
+ return textureUrl;
854
+ }
855
+ export function addCompositionItemByComposition(main, inputChild, compositionItemId, parentId) {
856
+ const child = JSON.parse(JSON.stringify(inputChild));
857
+ const delay = 0;
858
+ const duration = child.compositions?.[0]?.duration ?? 5;
859
+ const endBehavior = spec.EndBehavior.restart;
860
+ const compositionTransform = {
861
+ position: {
862
+ x: 0,
863
+ y: 0,
864
+ z: 0,
865
+ },
866
+ eulerHint: {
867
+ x: 0,
868
+ y: 0,
869
+ z: 0,
870
+ },
871
+ scale: {
872
+ x: 1,
873
+ y: 1,
874
+ z: 1,
875
+ },
876
+ };
877
+ const compositionOptions = {
878
+ refId: child.compositionId ?? child.compositions[0].id,
879
+ startColor: [1, 1, 1, 1],
880
+ };
881
+ const compositionContent = {
882
+ options: compositionOptions,
883
+ };
884
+ const specificationItem = {
885
+ id: compositionItemId,
886
+ components: [],
887
+ name: 'ref_' + child.compositions[0].name,
888
+ duration,
889
+ dataType: spec.DataType.VFXItemData,
890
+ parentId,
891
+ type: spec.ItemType.composition,
892
+ visible: true,
893
+ endBehavior,
894
+ delay,
895
+ renderLevel: spec.RenderLevel.BPlus,
896
+ content: compositionContent,
897
+ transform: compositionTransform,
898
+ };
899
+ main.items.push(specificationItem);
900
+ const activationPlayableAsset = {
901
+ id: generateGUID(),
902
+ dataType: 'ActivationPlayableAsset',
903
+ };
904
+ const activationTrack = {
905
+ id: generateGUID(),
906
+ dataType: 'ActivationTrack',
907
+ children: [],
908
+ clips: [
909
+ {
910
+ start: delay,
911
+ duration,
912
+ endBehavior,
913
+ asset: {
914
+ id: activationPlayableAsset.id,
915
+ },
916
+ },
917
+ ],
918
+ };
919
+ const subCompositionPlayableAsset = {
920
+ id: generateGUID(),
921
+ dataType: 'SubCompositionPlayableAsset',
922
+ };
923
+ const subCompositionTrack = {
924
+ id: generateGUID(),
925
+ dataType: 'SubCompositionTrack',
926
+ children: [],
927
+ clips: [
928
+ {
929
+ start: 0,
930
+ duration,
931
+ endBehavior,
932
+ asset: {
933
+ id: subCompositionPlayableAsset.id,
934
+ },
935
+ },
936
+ ],
937
+ };
938
+ const bindingTrackData = {
939
+ id: generateGUID(),
940
+ dataType: spec.DataType.ObjectBindingTrack,
941
+ children: [],
942
+ clips: [],
943
+ };
944
+ main.miscs.push(bindingTrackData);
945
+ const tracks = [];
946
+ const playableAssets = [];
947
+ tracks.push(activationTrack, subCompositionTrack);
948
+ playableAssets.push(activationPlayableAsset, subCompositionPlayableAsset);
949
+ if (Number(main.version) <= 3.3) {
950
+ const mainCompositionIndex = main.compositions.findIndex((c) => c.id === main.compositionId);
951
+ if (mainCompositionIndex >= 0) {
952
+ // @ts-expect-error type to fix
953
+ main.compositions[mainCompositionIndex].items.push({ id: specificationItem.id });
954
+ // @ts-expect-error type to fix
955
+ const timelineAsset = main.miscs.find((misc) => misc.id === main.compositions[mainCompositionIndex].timelineAsset.id);
956
+ timelineAsset?.tracks.push({ id: bindingTrackData.id });
957
+ // @ts-expect-error type to fix
958
+ main.compositions[mainCompositionIndex].sceneBindings.push({
959
+ key: { id: bindingTrackData.id },
960
+ value: { id: specificationItem.id },
961
+ });
962
+ }
963
+ }
964
+ else {
965
+ const compositionComponent = main.components.find((c) => c.id === main.compositions[0].components[0].id && c.dataType === spec.DataType.CompositionComponent);
966
+ compositionComponent.items.push({ id: specificationItem.id });
967
+ const timelineAsset = main.miscs.find((misc) => misc.id === compositionComponent.timelineAsset.id);
968
+ timelineAsset?.tracks.push({ id: bindingTrackData.id });
969
+ compositionComponent.sceneBindings.push({
970
+ key: { id: bindingTrackData.id },
971
+ value: { id: specificationItem.id },
972
+ });
973
+ }
974
+ tracks.forEach((track) => {
975
+ if (!track.path) {
976
+ bindingTrackData.children.push({ id: track.id });
977
+ }
978
+ });
979
+ main.miscs.push(...tracks);
980
+ main.miscs.push(...playableAssets);
981
+ Object.keys(main).forEach(property => {
982
+ if (property === 'compositionId') {
983
+ main.compositionId ??= main.compositions[0].id;
984
+ return;
985
+ }
986
+ else if (property === 'components') {
987
+ // 补充CompositionComponent.item.id
988
+ child.components.forEach(component => {
989
+ if (component.dataType === spec.DataType.CompositionComponent && !component.item.id) {
990
+ child.compositions.forEach(composition => {
991
+ if (composition.components[0]?.id === component.id) {
992
+ component.item.id = composition.id;
993
+ }
994
+ });
995
+ }
996
+ });
997
+ }
998
+ // @ts-expect-error type to fix
999
+ if (child[property] && Array.isArray(child[property])) {
1000
+ // @ts-expect-error type to fix
1001
+ main[property] ??= [];
1002
+ // @ts-expect-error type to fix
1003
+ main[property].push(...child[property]);
1004
+ }
1005
+ });
1006
+ return main;
1007
+ }
1008
+ /**
1009
+ * @description 删除预合成数据
1010
+ * @param scene 场景数据
1011
+ * @param itemId 预合成ID
1012
+ * @returns 场景数据
1013
+ */
1014
+ export function deleteCompositionItemFromScene(scene, itemIds) {
1015
+ const compositionIds = [];
1016
+ itemIds.forEach(id => {
1017
+ const item = scene.items.find(item => item.id === id);
1018
+ if (!item) {
1019
+ return;
1020
+ }
1021
+ deleteItemInfoFromScene(scene, id);
1022
+ if (item.type === spec.ItemType.composition) {
1023
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1024
+ const compositionId = item?.content.options.refId;
1025
+ compositionIds.push(compositionId);
1026
+ }
1027
+ });
1028
+ compositionIds.forEach(compositionId => {
1029
+ deleteCompositionFromScene(scene, compositionId);
1030
+ });
1031
+ return scene;
1032
+ }
1033
+ /**
1034
+ * @description 删除合成信息
1035
+ * @param scene 场景数据
1036
+ * @param compositionId 合成ID
1037
+ */
1038
+ export function deleteCompositionFromScene(scene, compositionId) {
1039
+ const compositionComponent = scene.components.find(com => com.item.id === compositionId);
1040
+ if (compositionComponent) {
1041
+ const timelineAssetId = compositionComponent.timelineAsset.id;
1042
+ scene.miscs = scene.miscs.filter(value => value.id !== timelineAssetId);
1043
+ scene.compositions = scene.compositions.filter(com => com.id !== compositionId);
1044
+ scene.components = scene.components.filter(com => com.id !== compositionComponent.id);
1045
+ }
1046
+ }
1047
+ /**
1048
+ * @description 重置元素ID - 加载时会新建guid,需要保持一致
1049
+ * @param scene 场景数据
1050
+ * @param idMap id名称列表
1051
+ */
1052
+ export function resetSubCompositionItemId(scene, idMap) {
1053
+ scene.items.forEach(item => {
1054
+ const originId = item.id;
1055
+ const newId = idMap.get(originId);
1056
+ if (newId) {
1057
+ item.id = newId;
1058
+ const components = scene.components.filter(c => c.item.id === originId);
1059
+ components.forEach(com => {
1060
+ com.item.id = newId;
1061
+ });
1062
+ const compositionComponents = scene.components.filter(comp => comp.dataType === spec.DataType.CompositionComponent);
1063
+ compositionComponents.forEach(com => {
1064
+ com.items.forEach(comItem => {
1065
+ if (comItem.id === originId) {
1066
+ comItem.id = newId;
1067
+ }
1068
+ });
1069
+ com.sceneBindings.forEach(bindingValue => {
1070
+ if (bindingValue.value.id === originId) {
1071
+ bindingValue.value.id = newId;
1072
+ }
1073
+ });
1074
+ });
1075
+ }
1076
+ });
1077
+ }
1078
+ /**
1079
+ * @description 设置场景中元素名称
1080
+ * @param scene 场景数据
1081
+ * @param itemId 元素ID
1082
+ * @param name 名称
1083
+ */
1084
+ export function setJSONItemName(scene, itemId, name) {
1085
+ const item = scene.items.find(item => item.id === itemId);
1086
+ if (!item) {
1087
+ console.warn(`item ${itemId} is not existed.`);
1088
+ return;
1089
+ }
1090
+ item.name = name;
1091
+ }
1092
+ /**
1093
+ * @description 将元素从一个合成移动到另一个合成
1094
+ * @param scene 场景数据
1095
+ * @param itemId 元素ID
1096
+ * @param targetCompositionId 目标合成ID,为null时表示移动到主合成
1097
+ * @param options 可选的位置、大小、缩放、旋转信息
1098
+ * @returns 是否移动成功
1099
+ */
1100
+ export function moveItemBetweenCompositions(scene, itemId, targetCompositionId, options) {
1101
+ // 1. 查找元素
1102
+ const item = scene.items.find(item => item.id === itemId);
1103
+ if (!item) {
1104
+ console.warn(`moveItemBetweenCompositions: item ${itemId} not found`);
1105
+ return false;
1106
+ }
1107
+ // 2. 查找元素当前所在的合成(源合成)
1108
+ const sourceCompositionComponent = findCompositionComponentByItemId(scene, itemId);
1109
+ if (!sourceCompositionComponent) {
1110
+ console.warn(`moveItemBetweenCompositions: source composition not found for item ${itemId}`);
1111
+ return false;
1112
+ }
1113
+ // 3. 查找目标合成
1114
+ const targetCompositionComponent = targetCompositionId
1115
+ ? findCompositionComponentByCompositionId(scene, targetCompositionId)
1116
+ : getMainCompositionComponent(scene);
1117
+ if (!targetCompositionComponent) {
1118
+ console.warn(`moveItemBetweenCompositions: target composition not found ${targetCompositionId ?? '(main)'}`);
1119
+ return false;
1120
+ }
1121
+ // 源合成和目标合成相同,无需移动
1122
+ if (sourceCompositionComponent.id === targetCompositionComponent.id) {
1123
+ return true;
1124
+ }
1125
+ // 4. 从源合成中移除元素的绑定关系
1126
+ const removedBindingInfo = removeItemBindingFromComposition(scene, sourceCompositionComponent, itemId);
1127
+ if (!removedBindingInfo) {
1128
+ console.warn(`moveItemBetweenCompositions: failed to remove item ${itemId} from source composition`);
1129
+ return false;
1130
+ }
1131
+ // 5. 将元素添加到目标合成
1132
+ addItemBindingToComposition(scene, targetCompositionComponent, itemId, removedBindingInfo.trackIds);
1133
+ // 6. 更新元素的变换属性
1134
+ if (options) {
1135
+ updateItemTransform(item, options);
1136
+ }
1137
+ return true;
1138
+ }
1139
+ /**
1140
+ * @description 根据元素ID查找包含该合成的CompositionComponent
1141
+ */
1142
+ function findCompositionComponentByItemId(scene, itemId) {
1143
+ if ((+scene.version) >= 3.3) {
1144
+ return scene.components.find(comp => comp.dataType === spec.DataType.CompositionComponent &&
1145
+ comp.items.some(item => item.id === itemId));
1146
+ }
1147
+ else {
1148
+ // V3.3 以下版本,遍历 compositions 查找
1149
+ const targetComposition = scene.compositions.find((c) => c.items?.some((item) => item.id === itemId));
1150
+ if (targetComposition) {
1151
+ return scene.components.find(comp => comp.dataType === spec.DataType.CompositionComponent &&
1152
+ comp.item.id === targetComposition.id);
1153
+ }
1154
+ }
1155
+ return undefined;
1156
+ }
1157
+ /**
1158
+ * @description 根据合成ID查找CompositionComponent
1159
+ */
1160
+ function findCompositionComponentByCompositionId(scene, compositionId) {
1161
+ return scene.components.find(comp => comp.dataType === spec.DataType.CompositionComponent &&
1162
+ comp.item.id === compositionId);
1163
+ }
1164
+ /**
1165
+ * @description 获取主合成组件
1166
+ */
1167
+ function getMainCompositionComponent(scene) {
1168
+ const mainComposition = scene.compositions.find(c => c.id === scene.compositionId) ?? scene.compositions[0];
1169
+ if (!mainComposition) {
1170
+ return undefined;
1171
+ }
1172
+ return scene.components.find(comp => comp.dataType === spec.DataType.CompositionComponent &&
1173
+ comp.item.id === mainComposition.id);
1174
+ }
1175
+ /**
1176
+ * @description 从合成中移除元素的绑定关系
1177
+ * @returns 移除的轨道ID列表,用于后续添加到新合成
1178
+ */
1179
+ function removeItemBindingFromComposition(scene, compositionComponent, itemId) {
1180
+ // 在 V3.3+ 中,从 CompositionComponent 中移除
1181
+ // 在 V3.3- 中,从 CompositionData 中移除
1182
+ const trackIds = [];
1183
+ let objectBindingTrackId = null;
1184
+ if ((+scene.version) >= 3.3) {
1185
+ // 1. 从 items 中移除
1186
+ compositionComponent.items = compositionComponent.items.filter(item => item.id !== itemId);
1187
+ // 2. 从 sceneBindings 中找到并移除绑定关系
1188
+ const bindingIndex = compositionComponent.sceneBindings.findIndex(binding => binding.value.id === itemId);
1189
+ if (bindingIndex >= 0) {
1190
+ const binding = compositionComponent.sceneBindings[bindingIndex];
1191
+ objectBindingTrackId = binding.key.id;
1192
+ compositionComponent.sceneBindings.splice(bindingIndex, 1);
1193
+ }
1194
+ }
1195
+ else {
1196
+ // V3.3 以下版本
1197
+ const composition = scene.compositions.find((c) => {
1198
+ const compComponent = scene.components.find(comp => comp.dataType === spec.DataType.CompositionComponent &&
1199
+ comp.id === compositionComponent.id);
1200
+ return compComponent && c.id === compComponent.item.id;
1201
+ });
1202
+ if (composition) {
1203
+ composition.items = composition.items.filter((item) => item.id !== itemId);
1204
+ const bindingIndex = composition.sceneBindings?.findIndex((binding) => binding.value.id === itemId);
1205
+ if (bindingIndex >= 0) {
1206
+ const binding = composition.sceneBindings[bindingIndex];
1207
+ objectBindingTrackId = binding.key.id;
1208
+ composition.sceneBindings.splice(bindingIndex, 1);
1209
+ }
1210
+ }
1211
+ }
1212
+ if (!objectBindingTrackId) {
1213
+ return null;
1214
+ }
1215
+ // 3. 从 TimelineAsset 中移除 ObjectBindingTrack
1216
+ const timelineAssetId = compositionComponent.timelineAsset.id;
1217
+ const timelineAsset = scene.miscs.find(m => m.id === timelineAssetId);
1218
+ if (timelineAsset?.tracks) {
1219
+ timelineAsset.tracks = timelineAsset.tracks.filter((track) => track.id !== objectBindingTrackId);
1220
+ }
1221
+ // 4. 获取 ObjectBindingTrack 的子轨道ID列表
1222
+ const objectBindingTrack = scene.miscs.find(m => m.id === objectBindingTrackId);
1223
+ if (objectBindingTrack?.children) {
1224
+ objectBindingTrack.children.forEach((child) => {
1225
+ trackIds.push(child.id);
1226
+ });
1227
+ }
1228
+ return { trackIds, objectBindingTrackId };
1229
+ }
1230
+ /**
1231
+ * @description 将元素绑定添加到目标合成
1232
+ */
1233
+ function addItemBindingToComposition(scene, compositionComponent, itemId, trackIds) {
1234
+ // 1. 添加到 items
1235
+ compositionComponent.items.push({ id: itemId });
1236
+ // 2. 创建新的 ObjectBindingTrack
1237
+ const bindingTrackData = {
1238
+ id: generateGUID(),
1239
+ dataType: spec.DataType.ObjectBindingTrack,
1240
+ children: [],
1241
+ clips: [],
1242
+ };
1243
+ // 添加子轨道引用
1244
+ for (const trackId of trackIds) {
1245
+ bindingTrackData.children.push({ id: trackId });
1246
+ }
1247
+ // 3. 添加到 sceneBindings
1248
+ compositionComponent.sceneBindings.push({
1249
+ key: { id: bindingTrackData.id },
1250
+ value: { id: itemId },
1251
+ });
1252
+ // 4. 添加到 TimelineAsset 的 tracks
1253
+ const timelineAssetId = compositionComponent.timelineAsset.id;
1254
+ const timelineAsset = scene.miscs.find(m => m.id === timelineAssetId);
1255
+ if (timelineAsset?.tracks) {
1256
+ timelineAsset.tracks.push({ id: bindingTrackData.id });
1257
+ }
1258
+ // 5. 添加 ObjectBindingTrack 到 miscs
1259
+ scene.miscs.push(bindingTrackData);
1260
+ }
1261
+ /**
1262
+ * @description 更新元素的变换属性
1263
+ */
1264
+ function updateItemTransform(item, options) {
1265
+ item.transform ??= {
1266
+ position: { x: 0, y: 0, z: 0 },
1267
+ eulerHint: { x: 0, y: 0, z: 0 },
1268
+ scale: { x: 1, y: 1, z: 1 },
1269
+ size: { x: 1, y: 1 },
1270
+ };
1271
+ if (options.position) {
1272
+ item.transform.position = { ...options.position };
1273
+ }
1274
+ if (options.size) {
1275
+ item.transform.size = { ...options.size };
1276
+ }
1277
+ if (options.scale) {
1278
+ item.transform.scale = { ...options.scale };
1279
+ }
1280
+ if (options.rotation) {
1281
+ item.transform.eulerHint = { ...options.rotation };
1282
+ }
1283
+ }
1284
+ export function getCompositionIdByRefCompositionItemId(scene, compositionItemId) {
1285
+ const compositionItem = scene.items.find(item => item.id === compositionItemId);
1286
+ if (!compositionItem) {
1287
+ console.warn(`Target composition item ${compositionItemId} is not existed.`);
1288
+ return undefined;
1289
+ }
1290
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1291
+ return compositionItem.content.options.refId;
1292
+ }
1293
+ /**
1294
+ * @description 获取预合成元素所指向的合成数据
1295
+ * @param scene 场景数据
1296
+ * @param compositionItemId 预合成元素ID
1297
+ * @returns 对应的合成数据 (CompositionData),如果未找到则返回 undefined
1298
+ */
1299
+ export function getCompositionDataByRefCompositionItemId(scene, compositionItemId) {
1300
+ const compositionItem = scene.items.find(item => item.id === compositionItemId);
1301
+ if (!compositionItem) {
1302
+ console.warn(`Target composition item ${compositionItemId} is not existed.`);
1303
+ return undefined;
1304
+ }
1305
+ // 预合成元素通过 content.options.refId 存储引用的合成ID
1306
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1307
+ const refId = compositionItem.content?.options?.refId;
1308
+ if (!refId) {
1309
+ console.warn(`Composition item ${compositionItemId} has no refId.`);
1310
+ return undefined;
1311
+ }
1312
+ // 根据 refId 查找对应的合成数据
1313
+ const compositionData = scene.compositions.find(composition => composition.id === refId);
1314
+ if (!compositionData) {
1315
+ console.warn(`Composition data with id ${refId} not found.`);
1316
+ return undefined;
1317
+ }
1318
+ return compositionData;
1319
+ }
1320
+ /**
1321
+ * @description 提取子合成元素及其所有关联资源为独立的 JSONScene
1322
+ * @param scene 主场景数据
1323
+ * @param compositionItemId 预合成元素ID(子合成元素ID)
1324
+ * @returns 独立的子合成场景 JSONScene,如果未找到则返回 undefined
1325
+ */
1326
+ export function extractSubCompositionToScene(scene, compositionItemId) {
1327
+ // 1. 查找预合成元素
1328
+ const compositionItem = scene.items.find(item => item.id === compositionItemId);
1329
+ if (!compositionItem) {
1330
+ console.warn(`Target composition item ${compositionItemId} is not existed.`);
1331
+ return undefined;
1332
+ }
1333
+ // 验证元素类型是预合成类型
1334
+ if (compositionItem.type !== spec.ItemType.composition) {
1335
+ console.warn(`Item ${compositionItemId} is not a composition item.`);
1336
+ return undefined;
1337
+ }
1338
+ // 2. 获取子合成ID (refId)
1339
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
1340
+ const refId = compositionItem.content?.options?.refId;
1341
+ if (!refId) {
1342
+ console.warn(`Composition item ${compositionItemId} has no refId.`);
1343
+ return undefined;
1344
+ }
1345
+ // 3. 提取合成数据
1346
+ const compositionData = scene.compositions.find(comp => comp.id === refId);
1347
+ if (!compositionData) {
1348
+ console.warn(`Composition data with id ${refId} not found.`);
1349
+ return undefined;
1350
+ }
1351
+ // 4. 查找对应的 CompositionComponent
1352
+ const compositionComponent = scene.components.find(comp => comp.dataType === spec.DataType.CompositionComponent && comp.item.id === refId);
1353
+ if (!compositionComponent) {
1354
+ console.warn(`CompositionComponent for composition ${refId} not found.`);
1355
+ return undefined;
1356
+ }
1357
+ // 收集所有需要的数据集合
1358
+ const itemIds = new Set();
1359
+ const componentIds = new Set();
1360
+ const miscIds = new Set();
1361
+ const textureIds = new Set();
1362
+ const imageIds = new Set();
1363
+ const videoIds = new Set();
1364
+ const materialIds = new Set();
1365
+ const shaderIds = new Set();
1366
+ const geometryIds = new Set();
1367
+ const animationIds = new Set();
1368
+ // 收集元素ID
1369
+ compositionComponent.items.forEach(itemRef => itemIds.add(itemRef.id));
1370
+ // 收集 CompositionComponent ID
1371
+ componentIds.add(compositionComponent.id);
1372
+ // 收集 TimelineAsset ID
1373
+ miscIds.add(compositionComponent.timelineAsset.id);
1374
+ // 5. 提取元素数据并收集关联资源
1375
+ const extractedItems = scene.items.filter(item => itemIds.has(item.id));
1376
+ // 6. 提取组件数据并收集资源引用
1377
+ const extractedComponents = scene.components.filter(comp => {
1378
+ if (comp.item.id === refId) {
1379
+ return true;
1380
+ }
1381
+ if (itemIds.has(comp.item.id)) {
1382
+ return true;
1383
+ }
1384
+ return false;
1385
+ });
1386
+ // 收集 resources 引用
1387
+ extractedComponents.forEach(comp => {
1388
+ componentIds.add(comp.id);
1389
+ // 收集 SpriteComponent 的 texture 引用
1390
+ if (comp.dataType === spec.DataType.SpriteComponent) {
1391
+ const spriteComp = comp;
1392
+ if (spriteComp.renderer?.texture?.id) {
1393
+ textureIds.add(spriteComp.renderer.texture.id);
1394
+ }
1395
+ }
1396
+ // 收集 VideoComponent 的 texture 引用
1397
+ if (comp.dataType === spec.DataType.VideoComponent) {
1398
+ const videoComp = comp;
1399
+ if (videoComp.renderer?.texture?.id) {
1400
+ textureIds.add(videoComp.renderer.texture.id);
1401
+ }
1402
+ }
1403
+ });
1404
+ // 7. 提取贴图和图片/视频资源
1405
+ const extractedTextures = [];
1406
+ if (scene.textures) {
1407
+ scene.textures.forEach(texture => {
1408
+ if (texture.id && textureIds.has(texture.id)) {
1409
+ extractedTextures.push(texture);
1410
+ // 收集图片或视频资源ID
1411
+ const sourceId = texture.source?.id;
1412
+ if (sourceId) {
1413
+ // 根据 texture 类型判断是图片还是视频
1414
+ const textureType = texture.sourceType;
1415
+ if (textureType === 1) { // Video source type
1416
+ videoIds.add(sourceId);
1417
+ }
1418
+ else {
1419
+ imageIds.add(sourceId);
1420
+ }
1421
+ }
1422
+ }
1423
+ });
1424
+ }
1425
+ const extractedImages = scene.images.filter(img => imageIds.has(img.id));
1426
+ const extractedVideos = scene.videos?.filter(video => videoIds.has(video.id)) ?? [];
1427
+ // 8. 提取 miscs 数据 (TimelineAsset, tracks, playableAssets)
1428
+ const extractedMiscs = [];
1429
+ // 提取 TimelineAsset
1430
+ const timelineAsset = scene.miscs.find(m => m.id === compositionComponent.timelineAsset.id);
1431
+ if (timelineAsset) {
1432
+ extractedMiscs.push({ ...timelineAsset, tracks: [] }); // 先添加空的tracks,后面再填充
1433
+ }
1434
+ // 收集 sceneBindings 相关的 track IDs
1435
+ const bindingTrackIds = compositionComponent.sceneBindings.map(binding => binding.key.id);
1436
+ bindingTrackIds.forEach(id => miscIds.add(id));
1437
+ // 收集 item 引用的所有 tracks
1438
+ compositionComponent.sceneBindings.forEach(binding => {
1439
+ const objectTrackId = binding.key.id;
1440
+ const objectTrack = scene.miscs.find(m => m.id === objectTrackId);
1441
+ if (objectTrack) {
1442
+ // 提取 ObjectBindingTrack 本身
1443
+ extractedMiscs.push({ ...objectTrack });
1444
+ // 将 track 添加到 TimelineAsset
1445
+ if (timelineAsset) {
1446
+ const timelineIndex = extractedMiscs.findIndex(m => m.id === timelineAsset.id);
1447
+ if (timelineIndex >= 0) {
1448
+ extractedMiscs[timelineIndex].tracks = extractedMiscs[timelineIndex].tracks || [];
1449
+ extractedMiscs[timelineIndex].tracks.push({ id: objectTrackId });
1450
+ }
1451
+ }
1452
+ // 提取子 tracks
1453
+ const childTrackIds = (objectTrack.children || []).map((child) => child.id);
1454
+ childTrackIds.forEach(trackId => {
1455
+ miscIds.add(trackId);
1456
+ const track = scene.miscs.find(m => m.id === trackId);
1457
+ if (track) {
1458
+ extractedMiscs.push({ ...track });
1459
+ // 从子 track 中提取 playableAssets
1460
+ const trackClips = track.clips;
1461
+ if (trackClips) {
1462
+ trackClips.forEach(clip => {
1463
+ if (clip.asset?.id) {
1464
+ miscIds.add(clip.asset.id);
1465
+ const asset = scene.miscs.find(m => m.id === clip.asset.id);
1466
+ if (asset) {
1467
+ extractedMiscs.push({ ...asset });
1468
+ }
1469
+ }
1470
+ });
1471
+ }
1472
+ }
1473
+ });
1474
+ // 从 ObjectBindingTrack 中提取 playableAssets
1475
+ const objectTrackClips = objectTrack.clips;
1476
+ if (objectTrackClips) {
1477
+ objectTrackClips.forEach(clip => {
1478
+ if (clip.asset?.id) {
1479
+ miscIds.add(clip.asset.id);
1480
+ const asset = scene.miscs.find(m => m.id === clip.asset.id);
1481
+ if (asset) {
1482
+ extractedMiscs.push({ ...asset });
1483
+ }
1484
+ }
1485
+ });
1486
+ }
1487
+ }
1488
+ });
1489
+ // 9. 按需提取其他资源 (shaders, materials, geometries, animations)
1490
+ // 这些需要根据实际使用情况进行收集,这里先保留空数组
1491
+ const extractedShaders = scene.shaders?.filter(s => shaderIds.has(s.id)) || [];
1492
+ const extractedMaterials = scene.materials?.filter(m => materialIds.has(m.id)) || [];
1493
+ const extractedGeometries = scene.geometries?.filter(g => geometryIds.has(g.id)) || [];
1494
+ const extractedAnimations = scene.animations?.filter(a => animationIds.has(a.id)) || [];
1495
+ // 10. 构建新的 JSONScene
1496
+ const newScene = {
1497
+ version: spec.JSONSceneVersion['3_6'],
1498
+ playerVersion: { ...scene.playerVersion },
1499
+ type: scene.type,
1500
+ compositionId: refId,
1501
+ compositions: [{ ...compositionData }],
1502
+ items: extractedItems.map(item => ({ ...item })),
1503
+ components: extractedComponents.map(comp => ({ ...comp })),
1504
+ miscs: extractedMiscs,
1505
+ images: extractedImages.map(img => ({ ...img })),
1506
+ textures: extractedTextures,
1507
+ videos: extractedVideos,
1508
+ shaders: extractedShaders,
1509
+ materials: extractedMaterials,
1510
+ geometries: extractedGeometries,
1511
+ animations: extractedAnimations,
1512
+ plugins: scene.plugins ? [...scene.plugins] : [],
1513
+ bins: scene.bins ? [...scene.bins] : [],
1514
+ };
1515
+ return newScene;
1516
+ }
1517
+ export function fixStandardJSON(scene) {
1518
+ const compositionComponents = scene.components.filter(component => component.dataType === spec.DataType.CompositionComponent);
1519
+ compositionComponents.forEach(compositionComponent => {
1520
+ if (!compositionComponent.items) {
1521
+ compositionComponent.items ??= [];
1522
+ const composition = scene.compositions.find(comp => comp.id === compositionComponent.item.id);
1523
+ if (composition && 'items' in composition && composition?.items) {
1524
+ compositionComponent.items.push(...composition.items);
1525
+ delete composition.items;
1526
+ }
1527
+ }
1528
+ });
1529
+ }