@vvfx/sdk 0.2.7 → 0.2.9

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 (108) 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-runtime.js +132 -0
  33. package/dist/html-overlay/document-runtime.js +71 -0
  34. package/dist/html-overlay/document-transform.js +96 -0
  35. package/dist/html-overlay/dom-order.js +8 -0
  36. package/dist/html-overlay/host-message-runtime.js +24 -0
  37. package/dist/html-overlay/html-capture-runtime.js +282 -0
  38. package/dist/html-overlay/html-viewport-style.js +10 -0
  39. package/dist/html-overlay/index.js +1 -0
  40. package/dist/html-overlay/manager.js +1663 -0
  41. package/dist/html-overlay/mime-utils.js +21 -0
  42. package/dist/html-overlay/overlay-transform.js +57 -0
  43. package/dist/html-overlay/path-utils.js +35 -0
  44. package/dist/html-overlay/shell-runtime.js +12 -0
  45. package/dist/index.cjs +1 -7
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.js +13 -75317
  48. package/dist/index.mjs +75311 -0
  49. package/dist/index.mjs.map +1 -0
  50. package/dist/layer-order.js +8 -0
  51. package/dist/math/box2.js +396 -0
  52. package/dist/math/circle.js +131 -0
  53. package/dist/math/euler.js +35 -0
  54. package/dist/math/index.js +13 -0
  55. package/dist/math/line2.js +153 -0
  56. package/dist/math/line3.js +36 -0
  57. package/dist/math/matrix4.js +207 -0
  58. package/dist/math/plane.js +52 -0
  59. package/dist/math/quaternion.js +3 -0
  60. package/dist/math/ray-caster.js +25 -0
  61. package/dist/math/ray.js +47 -0
  62. package/dist/math/type.js +1 -0
  63. package/dist/math/utils.js +77 -0
  64. package/dist/math/vector2.js +70 -0
  65. package/dist/math/vector3.js +60 -0
  66. package/dist/screen-shot/index.js +1 -0
  67. package/dist/screen-shot/screen-shot.js +113 -0
  68. package/dist/sdk-item/base-item.js +160 -0
  69. package/dist/sdk-item/card-item.js +131 -0
  70. package/dist/sdk-item/effects-item.js +144 -0
  71. package/dist/sdk-item/frame-item.js +217 -0
  72. package/dist/sdk-item/generator-item.js +188 -0
  73. package/dist/sdk-item/group-item.js +116 -0
  74. package/dist/sdk-item/index.js +39 -0
  75. package/dist/sdk-item/sprite-item.js +147 -0
  76. package/dist/sdk-item/text-item.js +223 -0
  77. package/dist/sdk-item/types.d.ts +1 -11
  78. package/dist/sdk-item/types.js +15 -0
  79. package/dist/sdk-item/video-item.js +174 -0
  80. package/dist/sdk.js +1903 -0
  81. package/dist/service/UndoRedo.js +79 -0
  82. package/dist/shared/index.d.ts +1 -0
  83. package/dist/shared/index.js +2 -0
  84. package/dist/shared/player.d.ts +1 -1
  85. package/dist/shared/player.js +9 -0
  86. package/dist/shared/spec.d.ts +1 -0
  87. package/dist/shared/spec.js +1 -0
  88. package/dist/size-adapte/index.js +1 -0
  89. package/dist/size-adapte/size-adapt.js +395 -0
  90. package/dist/types.d.ts +5 -1
  91. package/dist/types.js +17 -0
  92. package/dist/utils/background-manager.js +189 -0
  93. package/dist/utils/common-utils.js +183 -0
  94. package/dist/utils/index.js +8 -0
  95. package/dist/utils/interaction-utils.js +205 -0
  96. package/dist/utils/json-data-utils.js +1529 -0
  97. package/dist/utils/layout-utils.js +1217 -0
  98. package/dist/utils/page-data-utils.js +5397 -0
  99. package/dist/utils/player-data-utils.js +48 -0
  100. package/dist/utils/types.js +8 -0
  101. package/dist/wireframe/common/box.js +18 -0
  102. package/dist/wireframe/common/dashed-line.js +21 -0
  103. package/dist/wireframe/common/line.js +17 -0
  104. package/dist/wireframe/common/pixi-ext.js +1 -0
  105. package/dist/wireframe/common/pixi.js +31 -0
  106. package/dist/wireframe/index.js +4 -0
  107. package/package.json +10 -3
  108. package/dist/index.js.map +0 -1
@@ -0,0 +1,121 @@
1
+ import JSZip from '@progress/jszip-esm';
2
+ import { merge } from 'lodash-es';
3
+ import { EventEmitter } from '../shared';
4
+ import { logger, downloadBlob } from '../utils';
5
+ import { ExportMedia } from './export-media';
6
+ import { MEDIA_TYPE, EXPORT_STATUS_MAP } from './const';
7
+ const EXPORT_MEDIA_MAP = {
8
+ [MEDIA_TYPE.MP4]: {
9
+ suffix: 'mp4',
10
+ error: 'export mp4 is error'
11
+ },
12
+ [MEDIA_TYPE.GIF]: {
13
+ suffix: 'gif',
14
+ error: 'export gif is error'
15
+ },
16
+ [MEDIA_TYPE.Images]: {
17
+ suffix: 'zip',
18
+ error: 'export Images is error'
19
+ },
20
+ [MEDIA_TYPE.APNG]: {
21
+ suffix: 'apng',
22
+ error: 'export APNG is error'
23
+ },
24
+ [MEDIA_TYPE.WebP]: {
25
+ suffix: 'webp',
26
+ error: 'export WebP is error'
27
+ },
28
+ [MEDIA_TYPE.AlphaMaskVideo]: {
29
+ suffix: 'mp4',
30
+ error: 'export AlphaMaskVideo is error'
31
+ },
32
+ [MEDIA_TYPE.WebM]: {
33
+ suffix: 'webm',
34
+ error: 'export WebM is error'
35
+ }
36
+ };
37
+ export class Exporter extends EventEmitter {
38
+ _options;
39
+ _status = EXPORT_STATUS_MAP.DEFAULT;
40
+ _exportMedia;
41
+ constructor(options) {
42
+ super();
43
+ this._options = options;
44
+ /**
45
+ * 这里是,导出多媒体物料,如 MP4 ...
46
+ * 如果是其他类型,如 JSON,则需要根据类型,实现对应的导出
47
+ */
48
+ this._exportMedia = new ExportMedia(options);
49
+ }
50
+ get status() {
51
+ return this._status;
52
+ }
53
+ setOptions(options) {
54
+ const newOptions = merge({}, this._options, options);
55
+ this._options = newOptions;
56
+ this._exportMedia.setOptions(newOptions);
57
+ }
58
+ downloadByMediaBuffers(params, buffers, extras) {
59
+ const { suffix, error } = EXPORT_MEDIA_MAP[this._options.mediaType];
60
+ if (!buffers?.length || !suffix) {
61
+ logger('info', ['@vvfx/sdk', 'downloadByMediaBuffers is error', 'buffers or suffix is empty']);
62
+ return;
63
+ }
64
+ const folderName = params[0]?.folderName;
65
+ const zip = new JSZip();
66
+ buffers.forEach((buffer, idx) => {
67
+ if (!buffer) {
68
+ return;
69
+ }
70
+ const { name } = params[idx] ?? {};
71
+ if (name) {
72
+ zip.file(`${name}.${suffix}`, buffer);
73
+ if (extras?.[idx]?.byteLength) {
74
+ zip.file(`${name}.jpeg`, extras[idx]);
75
+ }
76
+ }
77
+ });
78
+ zip
79
+ .generateAsync({ type: 'blob' })
80
+ .then((blob) => {
81
+ downloadBlob(blob, `${folderName}.zip`, 'application/zip');
82
+ })
83
+ .catch((e) => {
84
+ const errMessage = error ?? 'export media is error';
85
+ logger('error', ['@vvfx/sdk'], errMessage, e);
86
+ });
87
+ }
88
+ onExportProgress(progress) {
89
+ this.emit('progress', progress);
90
+ }
91
+ onExportDone(current, success, buffer, extra) {
92
+ this.emit('done', current, success, buffer, extra);
93
+ }
94
+ onExportError(error) {
95
+ this._status = EXPORT_STATUS_MAP.ERROR;
96
+ logger('error', ['@vvfx/sdk'], 'export is error', error);
97
+ }
98
+ onExport(params) {
99
+ this._status = EXPORT_STATUS_MAP.EXPORTING;
100
+ void this._exportMedia.onExport(params);
101
+ this._exportMedia.on('progress', this.onExportProgress.bind(this));
102
+ this._exportMedia.on('done', this.onExportDone.bind(this));
103
+ this._exportMedia.once('finish', (success, taskInfos, buffers, extras) => {
104
+ this._status = success ? EXPORT_STATUS_MAP.SUCCESS : EXPORT_STATUS_MAP.ERROR;
105
+ this.emit('complete', success, taskInfos, buffers, extras);
106
+ if (this._options.isOutputBuffer || !buffers || buffers.length === 0) {
107
+ return;
108
+ }
109
+ this.downloadByMediaBuffers(params, buffers, extras);
110
+ });
111
+ this._exportMedia.on('error', this.onExportError.bind(this));
112
+ }
113
+ onCancel() {
114
+ this._status = EXPORT_STATUS_MAP.ERROR;
115
+ this._exportMedia.onCancel();
116
+ this.emit('complete', false, [], [], []);
117
+ }
118
+ dispose() {
119
+ this._exportMedia.dispose();
120
+ }
121
+ }
@@ -0,0 +1 @@
1
+ export * from './exporter';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,190 @@
1
+ import { asserts, loadScriptAsync } from '../utils';
2
+ import { HELPER_CONFIG } from './config';
3
+ export function getTwoDecimal(num) {
4
+ return Math.round(num * 100) / 100;
5
+ }
6
+ export async function loadImageAsync(img, noCrossOrigin = false) {
7
+ const imgEl = typeof img === 'string' ? (() => {
8
+ const imgEl = new Image();
9
+ if (!noCrossOrigin && !img.startsWith('data:')) {
10
+ imgEl.crossOrigin = '*';
11
+ }
12
+ imgEl.src = img;
13
+ return imgEl;
14
+ })() : img;
15
+ return imgEl.complete
16
+ ? imgEl
17
+ : new Promise((resolve, reject) => {
18
+ imgEl.addEventListener('load', () => {
19
+ resolve(imgEl);
20
+ });
21
+ imgEl.addEventListener('error', e => {
22
+ reject(e);
23
+ });
24
+ });
25
+ }
26
+ export function drawToOutputCanvas(playerCanvas, canvas, backgroundColor, backgroundCanvas) {
27
+ const ctx = canvas.getContext('2d');
28
+ asserts(ctx);
29
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
30
+ // 绘制背景颜色与背景图片
31
+ if (backgroundColor) {
32
+ ctx.save();
33
+ ctx.fillStyle = backgroundColor;
34
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
35
+ ctx.restore();
36
+ }
37
+ if (backgroundCanvas) {
38
+ ctx.drawImage(backgroundCanvas, 0, 0, backgroundCanvas.width, backgroundCanvas.height, 0, 0, canvas.width, canvas.height);
39
+ }
40
+ ctx.drawImage(playerCanvas, 0, 0, playerCanvas.width, playerCanvas.height, 0, 0, canvas.width, canvas.height);
41
+ }
42
+ const threshold = 5;
43
+ export function getAlphaMaskImage(inputCanvas, outputCanvas) {
44
+ outputCanvas.width = inputCanvas.width * 2;
45
+ outputCanvas.height = inputCanvas.height;
46
+ const inputCtx = inputCanvas.getContext('2d');
47
+ if (!inputCtx) {
48
+ throw new Error('inputCtx is null');
49
+ }
50
+ const canvas = outputCanvas;
51
+ const ctx = canvas.getContext('2d');
52
+ if (!ctx) {
53
+ throw new Error('ctx is null');
54
+ }
55
+ ctx.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
56
+ // 拆分 rgb 和 alpha 通道
57
+ // 这里实际上不用处理 rgb 通道,在合成视频的时候,ffmpeg 会自动把 a 通道去掉
58
+ const imageData1 = inputCtx.getImageData(0, 0, inputCanvas.width, inputCanvas.height);
59
+ // 只用处理 alpha 通道
60
+ const imageData2 = inputCtx.getImageData(0, 0, inputCanvas.width, inputCanvas.height);
61
+ //
62
+ const data1 = imageData1.data;
63
+ for (let i = 0; i < data1.length; i += 4) {
64
+ const alpha = Math.max(0, (data1[i + 3] ?? 0) - threshold) / (255 - threshold) * 255;
65
+ if (alpha <= 0) {
66
+ data1[i] = 0;
67
+ data1[i + 1] = 0;
68
+ data1[i + 2] = 0;
69
+ data1[i + 3] = 255;
70
+ }
71
+ else {
72
+ data1[i] = (data1[i] ?? 0) * alpha / 255;
73
+ data1[i + 1] = (data1[i + 1] ?? 0) * alpha / 255;
74
+ data1[i + 2] = (data1[i + 2] ?? 0) * alpha / 255;
75
+ data1[i + 3] = 255;
76
+ }
77
+ }
78
+ ctx.putImageData(imageData1, 0, 0);
79
+ //
80
+ const data2 = imageData2.data;
81
+ for (let i = 0; i < data2.length; i += 4) {
82
+ const alpha = Math.max(0, (data2[i + 3] ?? 0) - threshold) / (255 - threshold) * 255;
83
+ data2[i] = alpha;
84
+ data2[i + 1] = alpha;
85
+ data2[i + 2] = alpha;
86
+ data2[i + 3] = 255;
87
+ }
88
+ ctx.putImageData(imageData2, inputCanvas.width, 0);
89
+ }
90
+ export function parseArgs(Core, args) {
91
+ const argsPtr = Core._malloc(args.length * Uint32Array.BYTES_PER_ELEMENT);
92
+ args.forEach((s, idx) => {
93
+ const buf = Core._malloc(s.length + 1);
94
+ Core.writeAsciiToMemory(s, buf);
95
+ Core.setValue(argsPtr + (Uint32Array.BYTES_PER_ELEMENT * idx), buf, 'i32');
96
+ });
97
+ return [args.length, argsPtr];
98
+ }
99
+ export async function canvasToArrayBuffer(canvas, mime, quality) {
100
+ return new Promise((resolve, reject) => {
101
+ canvas.toBlob(blob => {
102
+ if (!blob) {
103
+ reject('toBlob failed');
104
+ }
105
+ else {
106
+ resolve(blob.arrayBuffer());
107
+ }
108
+ }, mime, quality);
109
+ });
110
+ }
111
+ export function sleepForOneFrame() {
112
+ return new Promise(resolve => {
113
+ window.requestAnimationFrame(resolve);
114
+ });
115
+ }
116
+ export function padZero(num, length) {
117
+ return String(num).padStart(length, '0');
118
+ }
119
+ export async function loadWebpHelper() {
120
+ return new Promise(async (resolve, reject) => {
121
+ try {
122
+ await loadScriptAsync(HELPER_CONFIG.WEBP.SCRIPT, {
123
+ crossOrigin: 'anonymous',
124
+ });
125
+ const webpOptions = {
126
+ locateFile: (path, prefix) => {
127
+ if (path.endsWith('createWebPCore.wasm')) {
128
+ return HELPER_CONFIG.WEBP.WASM;
129
+ }
130
+ return prefix + path;
131
+ },
132
+ };
133
+ const core = await window.createWebPCore(webpOptions);
134
+ resolve(core);
135
+ }
136
+ catch (error) {
137
+ reject(error);
138
+ }
139
+ });
140
+ }
141
+ export async function loadFFmpegHelper(options) {
142
+ try {
143
+ // 动态导入 FFmpeg 相关模块
144
+ const [{ FFmpeg }, { toBlobURL }] = await Promise.all([
145
+ import('@ffmpeg/ffmpeg').catch((err) => {
146
+ const message = err instanceof Error ? err.message : 'Unknown error';
147
+ throw new Error(`Failed to load @ffmpeg/ffmpeg: ${message}`);
148
+ }),
149
+ import('@ffmpeg/util').catch((err) => {
150
+ const message = err instanceof Error ? err.message : 'Unknown error';
151
+ throw new Error(`Failed to load @ffmpeg/util: ${message}`);
152
+ }),
153
+ ]);
154
+ const { multiThreading } = options ?? {};
155
+ const ffmpeg = new FFmpeg();
156
+ const isSupportMultiThreading = !!multiThreading;
157
+ const config = {
158
+ classWorkerURL: await toBlobURL(HELPER_CONFIG.FFMPEG.CLASSWORKER, 'text/javascript'),
159
+ coreURL: '',
160
+ wasmURL: '',
161
+ };
162
+ if (isSupportMultiThreading) {
163
+ config.coreURL = await toBlobURL(HELPER_CONFIG.FFMPEG.MT.CORE, 'text/javascript');
164
+ config.wasmURL = await toBlobURL(HELPER_CONFIG.FFMPEG.MT.WASM, 'application/wasm');
165
+ config.workerURL = await toBlobURL(HELPER_CONFIG.FFMPEG.MT.WORKER, 'text/javascript');
166
+ }
167
+ else {
168
+ config.coreURL = await toBlobURL(HELPER_CONFIG.FFMPEG.DEFAULT.CORE, 'text/javascript');
169
+ config.wasmURL = await toBlobURL(HELPER_CONFIG.FFMPEG.DEFAULT.WASM, 'application/wasm');
170
+ }
171
+ await ffmpeg.load(config);
172
+ return ffmpeg;
173
+ }
174
+ catch (error) {
175
+ throw error instanceof Error ? error : new Error(`Unknown error occurred while loading FFmpeg: ${String(error)}`);
176
+ }
177
+ }
178
+ export function clearPlayerAssets(player) {
179
+ for (const composition of player.getCompositions()) {
180
+ for (const texture of composition.textures) {
181
+ if (!texture.isDestroyed) {
182
+ texture.dispose();
183
+ }
184
+ }
185
+ }
186
+ //@ts-expect-error @赤芍 to fix
187
+ if (!player.disposed) {
188
+ player.destroyCurrentCompositions();
189
+ }
190
+ }
@@ -0,0 +1,50 @@
1
+ export class WavAudio {
2
+ sampleRate;
3
+ duration;
4
+ channels;
5
+ constructor(options) {
6
+ const { sampleRate = 44100, duration = 1, channels = 2 } = options ?? {};
7
+ this.sampleRate = sampleRate;
8
+ this.duration = duration;
9
+ this.channels = channels;
10
+ }
11
+ getBuffer() {
12
+ const numSamples = this.sampleRate * this.duration;
13
+ const bytesPerSample = 2;
14
+ const dataLength = numSamples * this.channels * bytesPerSample;
15
+ const fileSize = 44 + dataLength;
16
+ const buffer = new ArrayBuffer(fileSize);
17
+ const view = new DataView(buffer);
18
+ let pos = 0;
19
+ const writeString = (str) => {
20
+ for (let i = 0; i < str.length; i++) {
21
+ view.setUint8(pos++, str.charCodeAt(i));
22
+ }
23
+ };
24
+ const writeUint32 = (v) => {
25
+ view.setUint32(pos, v, true);
26
+ pos += 4;
27
+ };
28
+ const writeUint16 = (v) => {
29
+ view.setUint16(pos, v, true);
30
+ pos += 2;
31
+ };
32
+ writeString('RIFF');
33
+ writeUint32(fileSize - 8);
34
+ writeString('WAVE');
35
+ writeString('fmt ');
36
+ writeUint32(16);
37
+ writeUint16(1);
38
+ writeUint16(this.channels);
39
+ writeUint32(this.sampleRate);
40
+ writeUint32(this.sampleRate * this.channels * bytesPerSample);
41
+ writeUint16(this.channels * bytesPerSample);
42
+ writeUint16(16);
43
+ writeString('data');
44
+ writeUint32(dataLength);
45
+ for (let i = 0; i < dataLength; i++) {
46
+ view.setUint8(pos++, 0);
47
+ }
48
+ return new Uint8Array(buffer);
49
+ }
50
+ }
@@ -0,0 +1,227 @@
1
+ import { Graphics } from '@pixi/graphics';
2
+ import { SDK } from '../../sdk';
3
+ import { Gizmo } from './gizmo';
4
+ import { Line2, Vector2 } from '../../math';
5
+ export class AdsorptionGizmo extends Gizmo {
6
+ /**
7
+ * @description 吸附对齐结果
8
+ */
9
+ results = [];
10
+ /**
11
+ * @description 绘制图形
12
+ */
13
+ graphics = new Graphics();
14
+ /**
15
+ * @description 吸附结果
16
+ */
17
+ absorptionResult = {};
18
+ type = 'adsorption';
19
+ get interactive() {
20
+ return SDK.config.gestureHandlerConfig.adsorptionGizmoEnabled;
21
+ }
22
+ set interactive(bool) {
23
+ SDK.config.gestureHandlerConfig.adsorptionGizmoEnabled = bool;
24
+ }
25
+ preparationAction() {
26
+ return undefined;
27
+ }
28
+ getCursorState() {
29
+ return undefined;
30
+ }
31
+ preAction() {
32
+ return undefined;
33
+ }
34
+ action() { }
35
+ cancelPreparation() { }
36
+ actionKey() { }
37
+ endActionKey() { }
38
+ endAction() {
39
+ return undefined;
40
+ }
41
+ interruption() {
42
+ return undefined;
43
+ }
44
+ /**
45
+ * @description 包围盒位移吸附功能
46
+ * @param shift 偏移值
47
+ * @param box 目标包围盒
48
+ * @param otherBoxes 吸附包围盒
49
+ * @param xEnabled 是否进行x轴吸附
50
+ * @param yEnabled 是否进行y周吸附
51
+ * @returns 无
52
+ */
53
+ boxTranslationAdsorption(shift, box, otherBoxes, xEnabled = true, yEnabled = true) {
54
+ this.results = [];
55
+ if (!this.interactive) {
56
+ console.warn('Adsorption gizmo is not opened, use #SDK.setAdsorptionConfig() open this feature.');
57
+ return;
58
+ }
59
+ if (box.isEmpty()) {
60
+ return;
61
+ }
62
+ const absorptionShift = new Vector2(this.absorptionResult?.x ?? 0, this.absorptionResult?.y ?? 0).add(shift);
63
+ const targetBox = box.clone().translate(absorptionShift);
64
+ const targetBoxCenter = targetBox.getCenter();
65
+ this.absorptionResult = {};
66
+ // 对包围盒进行点对齐判断,以及获得可以做居中对齐判断的四个方向的包围盒
67
+ otherBoxes.forEach(box => {
68
+ if (box.isEmpty()) {
69
+ return;
70
+ }
71
+ box.corners.forEach(boxCorner => {
72
+ targetBox.corners.forEach(targetCorner => {
73
+ this.getPointAbsorption(targetCorner, boxCorner, xEnabled, yEnabled);
74
+ });
75
+ this.getPointAbsorption(targetBoxCenter, boxCorner, xEnabled, yEnabled);
76
+ });
77
+ const boxCenter = box.getCenter();
78
+ this.getPointAbsorption(targetBoxCenter, boxCenter, xEnabled, yEnabled);
79
+ targetBox.corners.forEach(targetCorner => {
80
+ this.getPointAbsorption(targetCorner, boxCenter, xEnabled, yEnabled);
81
+ });
82
+ });
83
+ }
84
+ /**
85
+ * @description 点位移吸附功能
86
+ * @param shift 偏移值
87
+ * @param point 目标点
88
+ * @param otherPoints 吸附点集合
89
+ * @returns 无
90
+ */
91
+ pointTranslationAdsorption(shift, point, otherPoints, xEnabled = true, yEnabled = true) {
92
+ this.results = [];
93
+ if (!this.interactive) {
94
+ console.warn('Adsorption is not opened, use #SDK.setAdsorptionConfig() open this feature.');
95
+ return;
96
+ }
97
+ const absorptionShift = new Vector2(this.absorptionResult?.x ?? 0, this.absorptionResult?.y ?? 0).add(shift);
98
+ const targetPoint = point.clone().add(absorptionShift);
99
+ this.absorptionResult = {};
100
+ otherPoints.forEach(otherPoint => {
101
+ this.getPointAbsorption(targetPoint, otherPoint, xEnabled, yEnabled);
102
+ });
103
+ }
104
+ /**
105
+ * @description 获取点吸附结果
106
+ * @param point 目标点
107
+ * @param other 吸附点
108
+ */
109
+ getPointAbsorption(point, other, xEnabled = true, yEnabled = true) {
110
+ if (xEnabled && Math.abs(point.x - other.x) < SDK.config.gestureHandlerConfig.adsorptionGizmoConfig.distance) {
111
+ this.refreshPointAbsorptionResult(point, other, 'x');
112
+ }
113
+ if (yEnabled && Math.abs(point.y - other.y) < SDK.config.gestureHandlerConfig.adsorptionGizmoConfig.distance) {
114
+ this.refreshPointAbsorptionResult(point, other, 'y');
115
+ }
116
+ }
117
+ /**
118
+ * 刷新吸附结果
119
+ * @param point 目标点
120
+ * @param other 吸附点
121
+ * @param direction 吸附方向
122
+ */
123
+ refreshPointAbsorptionResult(point, other, direction) {
124
+ const distance = point[direction] - other[direction];
125
+ const targetPointProperty = direction === 'x' ? 'xPoints' : 'yPoints';
126
+ // 吸附结果不存在时
127
+ if (this.absorptionResult && this.absorptionResult[direction] === undefined) {
128
+ this.absorptionResult[direction] = distance;
129
+ this.absorptionResult[targetPointProperty] = [{
130
+ point,
131
+ targets: [other],
132
+ }];
133
+ // 当 吸附距离绝对值 小于 Math.abs(this.absorptionResult[direction]!) + 0.05 时,需要进行判断【0.05为容差,防止点逆投影运算带来的偏差】
134
+ }
135
+ else if (Math.abs(distance) - Math.abs(this.absorptionResult[direction] ?? 0) <= 0.005) {
136
+ // 如果distance与吸附距离之间差值绝对值小于0.1 表示属于同一对齐线
137
+ if (Math.abs(Math.abs(distance) - Math.abs(this.absorptionResult[direction] ?? 0)) < 0.05 && (distance * (this.absorptionResult[direction] ?? 0) > 0)) {
138
+ // 做兼容
139
+ if (this.absorptionResult[targetPointProperty] === undefined) {
140
+ this.absorptionResult[direction] = distance;
141
+ this.absorptionResult[targetPointProperty] = [{
142
+ point,
143
+ targets: [other],
144
+ }];
145
+ }
146
+ else {
147
+ const result = this.absorptionResult[targetPointProperty].filter(info => info.point.equals(point));
148
+ if (result === undefined || result.length === 0) {
149
+ this.absorptionResult[targetPointProperty]?.push({
150
+ point,
151
+ targets: [other],
152
+ });
153
+ }
154
+ else {
155
+ result[0]?.targets.push(other);
156
+ }
157
+ }
158
+ // 当吸附距离绝对值 小于 Math.abs(this.absorptionResult[direction]!) 且 大于 0.1时,替换原有吸附信息
159
+ }
160
+ else {
161
+ this.absorptionResult[direction] = distance;
162
+ this.absorptionResult[targetPointProperty] = [{
163
+ point,
164
+ targets: [other],
165
+ }];
166
+ }
167
+ }
168
+ }
169
+ /**
170
+ * 基于吸附结果生成对齐结果
171
+ */
172
+ refreshResults() {
173
+ if (this.absorptionResult === undefined) {
174
+ return;
175
+ }
176
+ const { x, y, xPoints, yPoints } = this.absorptionResult;
177
+ const absorptionDistance = new Vector2(x ?? 0, y ?? 0);
178
+ if (x !== undefined && xPoints !== undefined) {
179
+ xPoints.forEach(xPointInfo => {
180
+ const point = xPointInfo.point.clone().subtract(absorptionDistance);
181
+ const startPoint = point.clone();
182
+ const endPoint = point.clone();
183
+ xPointInfo.targets.forEach(target => {
184
+ startPoint.x = target.x;
185
+ startPoint.y = Math.min(startPoint.y, target.y);
186
+ endPoint.x = target.x;
187
+ endPoint.y = Math.max(endPoint.y, target.y);
188
+ });
189
+ this.results.push(new Line2(startPoint, endPoint));
190
+ });
191
+ }
192
+ if (y !== undefined && yPoints !== undefined) {
193
+ yPoints.forEach(yPointInfo => {
194
+ const point = yPointInfo.point.clone().subtract(absorptionDistance);
195
+ const startPoint = point.clone();
196
+ const endPoint = point.clone();
197
+ yPointInfo.targets.forEach(target => {
198
+ startPoint.y = target.y;
199
+ startPoint.x = Math.min(startPoint.x, target.x);
200
+ endPoint.y = target.y;
201
+ endPoint.x = Math.max(endPoint.x, target.x);
202
+ });
203
+ this.results.push(new Line2(startPoint, endPoint));
204
+ });
205
+ }
206
+ }
207
+ refreshRenderObject() {
208
+ this.graphics.clear();
209
+ const { lineWidth, lineColor } = SDK.config.gestureHandlerConfig.adsorptionGizmoConfig;
210
+ this.results.forEach(adsorptionLine => {
211
+ this.graphics.lineStyle(lineWidth, lineColor);
212
+ this.graphics.drawLine(adsorptionLine);
213
+ });
214
+ }
215
+ getRenderObjects() {
216
+ if (!this.interactive) {
217
+ return [];
218
+ }
219
+ this.refreshResults();
220
+ this.refreshRenderObject();
221
+ return [this.graphics];
222
+ }
223
+ clearAbsorptionResult() {
224
+ this.results = [];
225
+ this.absorptionResult = {};
226
+ }
227
+ }