@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.
- package/dist/config.js +231 -0
- package/dist/exporter/config.js +18 -0
- package/dist/exporter/const.js +71 -0
- package/dist/exporter/export-media.js +878 -0
- package/dist/exporter/exporter.js +121 -0
- package/dist/exporter/index.js +1 -0
- package/dist/exporter/types.js +1 -0
- package/dist/exporter/utils.js +190 -0
- package/dist/exporter/wav-audio.js +50 -0
- package/dist/gesture-handler/gizmo/adsorption-gizmo.js +227 -0
- package/dist/gesture-handler/gizmo/control-gizmo.js +149 -0
- package/dist/gesture-handler/gizmo/gizmo.js +17 -0
- package/dist/gesture-handler/gizmo/icon-gizmo.js +189 -0
- package/dist/gesture-handler/gizmo/index.js +6 -0
- package/dist/gesture-handler/gizmo/item-create-gizmo.js +256 -0
- package/dist/gesture-handler/gizmo/loading-gizmo.js +320 -0
- package/dist/gesture-handler/gizmo/mask-gizmo.js +648 -0
- package/dist/gesture-handler/gizmo/picture-cut-gizmo.js +550 -0
- package/dist/gesture-handler/gizmo/picture-expand-gizmo.js +491 -0
- package/dist/gesture-handler/gizmo/preference-gizmo.js +148 -0
- package/dist/gesture-handler/gizmo/selector-gizmo.d.ts +1 -1
- package/dist/gesture-handler/gizmo/selector-gizmo.js +468 -0
- package/dist/gesture-handler/gizmo/sprite-text-edit-gizmo.js +247 -0
- package/dist/gesture-handler/gizmo/text-gizmo.js +1033 -0
- package/dist/gesture-handler/gizmo/transform-gizmo.js +1199 -0
- package/dist/gesture-handler/gizmo/type.js +18 -0
- package/dist/gesture-handler/index.js +3 -0
- package/dist/gesture-handler/src/gesture-handler.js +770 -0
- package/dist/gesture-handler/src/icons.js +24 -0
- package/dist/gesture-handler/utils.js +40 -0
- package/dist/html-overlay/anchor-navigation-runtime.js +27 -0
- package/dist/html-overlay/auto-height-layout.d.ts +6 -0
- package/dist/html-overlay/auto-height-layout.js +7 -0
- package/dist/html-overlay/auto-height-runtime.js +132 -0
- package/dist/html-overlay/document-runtime.js +71 -0
- package/dist/html-overlay/document-transform.js +96 -0
- package/dist/html-overlay/dom-order.js +8 -0
- package/dist/html-overlay/host-message-runtime.js +24 -0
- package/dist/html-overlay/html-capture-runtime.js +282 -0
- package/dist/html-overlay/html-viewport-style.js +10 -0
- package/dist/html-overlay/index.js +1 -0
- package/dist/html-overlay/manager.d.ts +1 -0
- package/dist/html-overlay/manager.js +1677 -0
- package/dist/html-overlay/mime-utils.js +21 -0
- package/dist/html-overlay/overlay-transform.js +57 -0
- package/dist/html-overlay/path-utils.js +35 -0
- package/dist/html-overlay/shell-runtime.js +12 -0
- package/dist/index.cjs +34 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +13 -75311
- package/dist/index.mjs +75337 -0
- package/dist/index.mjs.map +1 -0
- package/dist/layer-order.js +8 -0
- package/dist/math/box2.js +396 -0
- package/dist/math/circle.js +131 -0
- package/dist/math/euler.js +35 -0
- package/dist/math/index.js +13 -0
- package/dist/math/line2.js +153 -0
- package/dist/math/line3.js +36 -0
- package/dist/math/matrix4.js +207 -0
- package/dist/math/plane.js +52 -0
- package/dist/math/quaternion.js +3 -0
- package/dist/math/ray-caster.js +25 -0
- package/dist/math/ray.js +47 -0
- package/dist/math/type.js +1 -0
- package/dist/math/utils.js +77 -0
- package/dist/math/vector2.js +70 -0
- package/dist/math/vector3.js +60 -0
- package/dist/screen-shot/index.js +1 -0
- package/dist/screen-shot/screen-shot.js +113 -0
- package/dist/sdk-item/base-item.js +160 -0
- package/dist/sdk-item/card-item.js +131 -0
- package/dist/sdk-item/effects-item.js +144 -0
- package/dist/sdk-item/frame-item.js +217 -0
- package/dist/sdk-item/generator-item.js +188 -0
- package/dist/sdk-item/group-item.js +116 -0
- package/dist/sdk-item/index.js +39 -0
- package/dist/sdk-item/sprite-item.js +147 -0
- package/dist/sdk-item/text-item.js +223 -0
- package/dist/sdk-item/types.d.ts +1 -1
- package/dist/sdk-item/types.js +15 -0
- package/dist/sdk-item/video-item.js +174 -0
- package/dist/sdk.js +1908 -0
- package/dist/service/UndoRedo.js +79 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +2 -0
- package/dist/shared/player.d.ts +1 -1
- package/dist/shared/player.js +9 -0
- package/dist/shared/spec.d.ts +1 -0
- package/dist/shared/spec.js +1 -0
- package/dist/size-adapte/index.js +1 -0
- package/dist/size-adapte/size-adapt.js +395 -0
- package/dist/types.d.ts +5 -1
- package/dist/types.js +17 -0
- package/dist/utils/background-manager.js +189 -0
- package/dist/utils/common-utils.js +183 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/interaction-utils.js +205 -0
- package/dist/utils/json-data-utils.js +1529 -0
- package/dist/utils/layout-utils.js +1217 -0
- package/dist/utils/page-data-utils.js +5397 -0
- package/dist/utils/player-data-utils.js +48 -0
- package/dist/utils/types.js +8 -0
- package/dist/wireframe/common/box.js +18 -0
- package/dist/wireframe/common/dashed-line.js +21 -0
- package/dist/wireframe/common/line.js +17 -0
- package/dist/wireframe/common/pixi-ext.js +1 -0
- package/dist/wireframe/common/pixi.js +31 -0
- package/dist/wireframe/index.js +4 -0
- package/package.json +10 -3
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description 背景管理器
|
|
3
|
+
* 负责管理画布背景的配置、渲染和跟随变换
|
|
4
|
+
*/
|
|
5
|
+
export class BackgroundManager {
|
|
6
|
+
/**
|
|
7
|
+
* @description 默认网格配置
|
|
8
|
+
*/
|
|
9
|
+
static DEFAULT_GRID_CONFIG = {
|
|
10
|
+
baseSize: 16,
|
|
11
|
+
zoomCycle: 2,
|
|
12
|
+
minSize: 8,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* @description 背景预设配置
|
|
16
|
+
*/
|
|
17
|
+
static PRESETS = {
|
|
18
|
+
'dot-board': {
|
|
19
|
+
color: '#F5F5F5',
|
|
20
|
+
image: 'radial-gradient(circle 1px at 8px 8px, #BBBBBB 50%, transparent 100%)',
|
|
21
|
+
repeat: 'repeat',
|
|
22
|
+
position: '0 0',
|
|
23
|
+
size: '16px 16px',
|
|
24
|
+
},
|
|
25
|
+
'chess-board': {
|
|
26
|
+
color: '#CCCCCC',
|
|
27
|
+
image: 'linear-gradient(45deg, #FFFFFF 25%, transparent 25%, transparent 75%, #FFFFFF 75%),linear-gradient(45deg, #FFFFFF 25%, transparent 25%, transparent 75%, #FFFFFF 75%)',
|
|
28
|
+
repeat: 'repeat',
|
|
29
|
+
position: '0 0, 8px 8px',
|
|
30
|
+
size: '16px 16px',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
container = null;
|
|
34
|
+
config;
|
|
35
|
+
/**
|
|
36
|
+
* @description 当前背景位置(用于跟随移动)
|
|
37
|
+
*/
|
|
38
|
+
offsetX = 0;
|
|
39
|
+
offsetY = 0;
|
|
40
|
+
constructor(config) {
|
|
41
|
+
this.config = { ...BackgroundManager.DEFAULT_GRID_CONFIG, ...config };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @description 绑定容器元素
|
|
45
|
+
* @param element 画布容器元素
|
|
46
|
+
*/
|
|
47
|
+
attach(element) {
|
|
48
|
+
this.container = element;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* @description 解绑容器元素
|
|
52
|
+
*/
|
|
53
|
+
detach() {
|
|
54
|
+
this.container = null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* @description 应用背景预设
|
|
58
|
+
* @param presetName 预设名称
|
|
59
|
+
* @param color 可选的背景色覆盖
|
|
60
|
+
*/
|
|
61
|
+
applyPreset(presetName, color) {
|
|
62
|
+
if (!this.container) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
const preset = BackgroundManager.PRESETS[presetName];
|
|
66
|
+
if (!preset) {
|
|
67
|
+
console.warn(`Unknown background preset: ${presetName}`);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const element = this.container.parentElement;
|
|
71
|
+
if (!element) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
// 清除旧样式
|
|
75
|
+
this.clearBackground(element);
|
|
76
|
+
// 应用新样式
|
|
77
|
+
if (preset.color || color) {
|
|
78
|
+
element.style.backgroundColor = color ?? preset.color ?? '';
|
|
79
|
+
}
|
|
80
|
+
if (preset.image) {
|
|
81
|
+
element.style.backgroundImage = preset.image;
|
|
82
|
+
}
|
|
83
|
+
if (preset.repeat) {
|
|
84
|
+
element.style.backgroundRepeat = preset.repeat;
|
|
85
|
+
}
|
|
86
|
+
if (preset.position) {
|
|
87
|
+
element.style.backgroundPosition = preset.position;
|
|
88
|
+
// 初始化内部位置追踪
|
|
89
|
+
this.offsetX = 0;
|
|
90
|
+
this.offsetY = 0;
|
|
91
|
+
}
|
|
92
|
+
if (preset.size) {
|
|
93
|
+
element.style.backgroundSize = preset.size;
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* @description 设置纯色背景
|
|
99
|
+
* @param color 颜色值
|
|
100
|
+
*/
|
|
101
|
+
setColorBackground(color) {
|
|
102
|
+
if (!this.container) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const element = this.container.parentElement;
|
|
106
|
+
if (!element) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
this.clearBackground(element);
|
|
110
|
+
element.style.backgroundColor = color;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* @description 设置图片背景
|
|
114
|
+
* @param url 图片 URL
|
|
115
|
+
*/
|
|
116
|
+
setImageBackground(url) {
|
|
117
|
+
if (!this.container) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const element = this.container.parentElement;
|
|
121
|
+
if (!element) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.clearBackground(element);
|
|
125
|
+
element.style.backgroundImage = `url(${url})`;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* @description 更新背景变换(跟随画布移动和缩放)
|
|
129
|
+
* @param shift 画布移动的偏移量
|
|
130
|
+
* @param zoom 当前缩放比例
|
|
131
|
+
*/
|
|
132
|
+
updateTransform(shift, zoom) {
|
|
133
|
+
if (!this.container) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const parentElement = this.container.parentElement;
|
|
137
|
+
if (!parentElement) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
// 更新位置偏移
|
|
141
|
+
this.offsetX += shift.x;
|
|
142
|
+
this.offsetY += shift.y;
|
|
143
|
+
// 计算循环缩放比例
|
|
144
|
+
const bgZoom = this.calculateZoomCycle(zoom);
|
|
145
|
+
// 根据循环缩放比例计算新的网格大小
|
|
146
|
+
const newSize = Math.max(this.config.minSize, this.config.baseSize * bgZoom);
|
|
147
|
+
// 更新背景样式
|
|
148
|
+
parentElement.style.backgroundPosition = `${this.offsetX}px ${this.offsetY}px , ${this.offsetX + newSize / 2}px ${this.offsetY + newSize / 2}px`;
|
|
149
|
+
parentElement.style.backgroundSize = `${newSize}px ${newSize}px`;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* @description 重置背景位置和大小
|
|
153
|
+
*/
|
|
154
|
+
reset() {
|
|
155
|
+
this.offsetX = 0;
|
|
156
|
+
this.offsetY = 0;
|
|
157
|
+
if (!this.container) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const parentElement = this.container.parentElement;
|
|
161
|
+
if (!parentElement) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
parentElement.style.backgroundPosition = '';
|
|
165
|
+
parentElement.style.backgroundSize = '';
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* @description 清除背景样式
|
|
169
|
+
*/
|
|
170
|
+
clearBackground(element) {
|
|
171
|
+
element.style.removeProperty('background-color');
|
|
172
|
+
element.style.removeProperty('background-image');
|
|
173
|
+
element.style.removeProperty('background-repeat');
|
|
174
|
+
element.style.removeProperty('background-position');
|
|
175
|
+
element.style.removeProperty('background-size');
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* @description 计算循环缩放比例
|
|
179
|
+
* zoom 在 [1, 2) 时,bgZoom = zoom
|
|
180
|
+
* zoom 在 [2, 4) 时,bgZoom = zoom / 2
|
|
181
|
+
* zoom 在 [0.5, 1) 时,bgZoom = zoom / 0.5
|
|
182
|
+
*/
|
|
183
|
+
calculateZoomCycle(zoom) {
|
|
184
|
+
const { zoomCycle } = this.config;
|
|
185
|
+
const logZoom = Math.log(zoom) / Math.log(zoomCycle);
|
|
186
|
+
const power = Math.floor(logZoom);
|
|
187
|
+
return zoom / Math.pow(zoomCycle, power);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { DEG2RAD, Matrix4, Quaternion, roundNumber, Vector3 } from '../math';
|
|
2
|
+
export function arrAdd(arr, item) {
|
|
3
|
+
if (!arr.includes(item)) {
|
|
4
|
+
arr.push(item);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function isObj(item) {
|
|
8
|
+
return typeof item === 'object' && !!item;
|
|
9
|
+
}
|
|
10
|
+
export function deepEquals(a, b) {
|
|
11
|
+
if (isObj(a) && isObj(b)) {
|
|
12
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
13
|
+
if (a.length === b.length) {
|
|
14
|
+
return a.every((item, index) => deepEquals(item, b[index]));
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const kas = [];
|
|
19
|
+
for (const key in a) {
|
|
20
|
+
if (Object.prototype.hasOwnProperty.call(a, key)) {
|
|
21
|
+
if (!deepEquals(a[key], b[key])) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
kas.push(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return Object.getOwnPropertyNames(b).length === kas.length;
|
|
28
|
+
}
|
|
29
|
+
return a === b;
|
|
30
|
+
}
|
|
31
|
+
// lodash.clonedeep 太大了,直接使用简单版
|
|
32
|
+
export function deepClone(target) {
|
|
33
|
+
if (target === undefined) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
return JSON.parse(JSON.stringify(target));
|
|
37
|
+
}
|
|
38
|
+
export function getColorByNormalizeColor(color) {
|
|
39
|
+
return color.map((c, index) => index < 3 ? roundNumber(c * 255, 0) : c);
|
|
40
|
+
}
|
|
41
|
+
export function getNormalizeColorByColor(color) {
|
|
42
|
+
return color.map((c, index) => index < 3 ? roundNumber(c / 255, 4) : c);
|
|
43
|
+
}
|
|
44
|
+
export function getTransformRatioByViewAndCamera(viewSize, cameraPosition, cameraFov, cameraNear, cameraFar, isReverse) {
|
|
45
|
+
const viewMatrix = new Matrix4().compose(cameraPosition, new Quaternion(), new Vector3(1, 1, 1)).invert();
|
|
46
|
+
const projectMatrix = new Matrix4().perspective(cameraFov * DEG2RAD, viewSize.x / viewSize.y, cameraNear, cameraFar, isReverse);
|
|
47
|
+
const vpMatrix = new Matrix4().multiplyMatrices(projectMatrix, viewMatrix);
|
|
48
|
+
const inverseVPMatrix = vpMatrix.clone().invert();
|
|
49
|
+
const z = vpMatrix.projectPoint(new Vector3()).z;
|
|
50
|
+
const start = new Vector3(0, 0, z).applyMatrix(inverseVPMatrix);
|
|
51
|
+
const end = new Vector3(2, 2, z).applyMatrix(inverseVPMatrix);
|
|
52
|
+
const xRatio = Math.abs(end.x - start.x) / viewSize.x;
|
|
53
|
+
const yRatio = Math.abs(end.y - start.y) / viewSize.y;
|
|
54
|
+
return { xRatio, yRatio };
|
|
55
|
+
}
|
|
56
|
+
export function assertExist(item, msg = 'item doesn\'t exist') {
|
|
57
|
+
if (item === undefined || item === null) {
|
|
58
|
+
throw new Error(msg);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function asserts(condition, msg = 'asserts failed') {
|
|
62
|
+
if (!condition) {
|
|
63
|
+
throw new Error(msg);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function logger(level, prefixs, ...args) {
|
|
67
|
+
const flag = prefixs.reduce((acc, curr) => acc ? `${acc} ${curr}` : curr, '');
|
|
68
|
+
const style = 'color: #108ee9;';
|
|
69
|
+
switch (level) {
|
|
70
|
+
case 'error':
|
|
71
|
+
console.error(`%c[${flag}]`, style, ...args);
|
|
72
|
+
break;
|
|
73
|
+
case 'info':
|
|
74
|
+
console.info(`%c[${flag}]`, style, ...args);
|
|
75
|
+
break;
|
|
76
|
+
case 'debug':
|
|
77
|
+
console.debug(`%c[${flag}]`, style, ...args);
|
|
78
|
+
break;
|
|
79
|
+
case 'warn':
|
|
80
|
+
console.warn(`%c[${flag}]`, style, ...args);
|
|
81
|
+
break;
|
|
82
|
+
case 'log':
|
|
83
|
+
console.log(`%c[${flag}]`, style, ...args);
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
console.log(`%c[${flag}]`, style, ...args);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function loadScriptAsync(src, attrs = {}) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
// 检查是否已加载过该脚本,避免重复加载
|
|
93
|
+
const existingScript = document.querySelector(`script[src="${src}"]`);
|
|
94
|
+
if (existingScript) {
|
|
95
|
+
if (existingScript.dataset.loaded === 'true') {
|
|
96
|
+
resolve(existingScript);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
existingScript.addEventListener('load', () => {
|
|
100
|
+
resolve(existingScript);
|
|
101
|
+
});
|
|
102
|
+
existingScript.addEventListener('error', () => {
|
|
103
|
+
reject(new Error(`Script already failed: ${src}`));
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// 不存在,则创建新的 script 元素
|
|
108
|
+
const scriptEl = document.createElement('script');
|
|
109
|
+
scriptEl.src = src;
|
|
110
|
+
scriptEl.async = true;
|
|
111
|
+
Object.keys(attrs).forEach(attrKey => {
|
|
112
|
+
scriptEl.setAttribute(attrKey, attrs[attrKey] ?? '');
|
|
113
|
+
});
|
|
114
|
+
scriptEl.onload = () => {
|
|
115
|
+
scriptEl.dataset.loaded = 'true';
|
|
116
|
+
resolve(scriptEl);
|
|
117
|
+
};
|
|
118
|
+
scriptEl.onerror = () => {
|
|
119
|
+
reject(new Error(`Failed to load script: ${src}`));
|
|
120
|
+
};
|
|
121
|
+
document.head.appendChild(scriptEl);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
export function downloadBlob(buffer, filename, mimeType = 'application/octet-stream') {
|
|
125
|
+
const blob = new Blob(Array.isArray(buffer) ? buffer : [buffer], { type: mimeType });
|
|
126
|
+
const url = URL.createObjectURL(blob);
|
|
127
|
+
const a = document.createElement('a');
|
|
128
|
+
a.href = url;
|
|
129
|
+
a.download = filename;
|
|
130
|
+
document.body.appendChild(a);
|
|
131
|
+
a.click();
|
|
132
|
+
document.body.removeChild(a);
|
|
133
|
+
URL.revokeObjectURL(url);
|
|
134
|
+
}
|
|
135
|
+
export function getUniqueName(name, names) {
|
|
136
|
+
if (!names.includes(name)) {
|
|
137
|
+
return name;
|
|
138
|
+
}
|
|
139
|
+
let i = 1;
|
|
140
|
+
let newName = `${name} (${i})`;
|
|
141
|
+
while (names.includes(newName)) {
|
|
142
|
+
i++;
|
|
143
|
+
newName = `${name} (${i})`;
|
|
144
|
+
}
|
|
145
|
+
return newName;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 深度比较两个值是否相等
|
|
149
|
+
*/
|
|
150
|
+
export function isEqual(a, b) {
|
|
151
|
+
if (Object.is(a, b)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
if (typeof a !== typeof b) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (a === null || typeof a !== 'object') {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
if (a instanceof Date && b instanceof Date) {
|
|
161
|
+
return a.getTime() === b.getTime();
|
|
162
|
+
}
|
|
163
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
164
|
+
return a.toString() === b.toString();
|
|
165
|
+
}
|
|
166
|
+
if (Array.isArray(a) !== Array.isArray(b)) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const keysA = Object.keys(a);
|
|
170
|
+
const keysB = Object.keys(b);
|
|
171
|
+
if (keysA.length !== keysB.length) {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
for (const key of keysA) {
|
|
175
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (!isEqual(a[key], b[key])) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './common-utils';
|
|
2
|
+
export * from './background-manager';
|
|
3
|
+
export * from './interaction-utils';
|
|
4
|
+
export * from './json-data-utils';
|
|
5
|
+
export * from './layout-utils';
|
|
6
|
+
export * from './types';
|
|
7
|
+
export * from './player-data-utils';
|
|
8
|
+
export * from './page-data-utils';
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { assertExist } from '@galacean/effects';
|
|
2
|
+
import { Ray, Euler, Plane, Vector2, Vector3, Matrix4, RayCaster, Quaternion, } from '../math';
|
|
3
|
+
import { SDK } from '../sdk';
|
|
4
|
+
export class InteractionUtils {
|
|
5
|
+
/**
|
|
6
|
+
* @description 交互平面
|
|
7
|
+
*/
|
|
8
|
+
plane = new Plane();
|
|
9
|
+
/**
|
|
10
|
+
* @description 交互射线
|
|
11
|
+
*/
|
|
12
|
+
ray = new Ray();
|
|
13
|
+
/**
|
|
14
|
+
* @description 交互状态视图信息
|
|
15
|
+
*/
|
|
16
|
+
viewportParam = {
|
|
17
|
+
width: 0,
|
|
18
|
+
height: 0,
|
|
19
|
+
scale: 1,
|
|
20
|
+
translation: new Vector2(),
|
|
21
|
+
rulerWidth: 0,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* @description 交互状态相机信息
|
|
25
|
+
*/
|
|
26
|
+
cameraParam = {
|
|
27
|
+
position: new Vector3(),
|
|
28
|
+
rotation: new Vector3(),
|
|
29
|
+
focusPosition: new Vector3(),
|
|
30
|
+
focusRotation: new Vector3(),
|
|
31
|
+
viewProjectionMatrix: new Matrix4(),
|
|
32
|
+
inverseViewProjectionMatrix: new Matrix4(),
|
|
33
|
+
};
|
|
34
|
+
pageDataUtils;
|
|
35
|
+
constructor(utils) {
|
|
36
|
+
this.pageDataUtils = utils;
|
|
37
|
+
}
|
|
38
|
+
refreshViewParam() {
|
|
39
|
+
const pageData = this.pageDataUtils.getPageData();
|
|
40
|
+
assertExist(pageData, 'You must run SDK first');
|
|
41
|
+
const viewSize = new Vector2(this.pageDataUtils.container.offsetWidth, this.pageDataUtils.container.offsetHeight);
|
|
42
|
+
const { zoom, translation } = pageData.property;
|
|
43
|
+
this.viewportParam.width = viewSize.x;
|
|
44
|
+
this.viewportParam.height = viewSize.y;
|
|
45
|
+
this.viewportParam.scale = zoom;
|
|
46
|
+
this.viewportParam.translation = new Vector2(...translation);
|
|
47
|
+
this.viewportParam.rulerWidth = 0;
|
|
48
|
+
}
|
|
49
|
+
refreshCameraParam() {
|
|
50
|
+
const { player } = this.pageDataUtils;
|
|
51
|
+
const playerCamera = player?.getCompositions()?.[0]?.camera;
|
|
52
|
+
if (playerCamera) {
|
|
53
|
+
const vpMatrix = Matrix4.fromArray(playerCamera.getViewProjectionMatrix().toArray());
|
|
54
|
+
// const viewportMatrix = new Matrix4().compose(viewTranslation, new Quaternion(), new Vector3(scale, scale, 1))
|
|
55
|
+
const viewMatrix = Matrix4.fromArray(playerCamera.getViewMatrix().toArray()).invert();
|
|
56
|
+
const quaternion = new Quaternion();
|
|
57
|
+
viewMatrix.decompose(this.cameraParam.position, quaternion, new Vector3());
|
|
58
|
+
this.cameraParam.rotation = new Euler().setFromQuaternion(quaternion).toDegreeVector3();
|
|
59
|
+
// 相机始终朝向 [0, 0, -1],所以当相机位置小于0时,以其与原点的z轴距离作为交互平面中心位置
|
|
60
|
+
this.cameraParam.focusPosition = new Vector3(...this.cameraParam.position.toVector2().toArray(), this.cameraParam.position.z > 0 ? 0 : this.cameraParam.position.z * 2);
|
|
61
|
+
this.cameraParam.focusPosition.applyEuler(new Euler().setFromDegreeVector3(this.cameraParam.rotation.clone()), this.cameraParam.position);
|
|
62
|
+
this.cameraParam.focusRotation = new Euler().setFromQuaternion(quaternion.invert()).toDegreeVector3();
|
|
63
|
+
this.cameraParam.viewProjectionMatrix = vpMatrix;
|
|
64
|
+
this.cameraParam.inverseViewProjectionMatrix = Matrix4.fromArray(playerCamera.getInverseViewProjectionMatrix().toArray());
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
refrehCameraParamByCameraInfo(camera) {
|
|
68
|
+
const { near, far, fov, aspect, position, clipMode } = camera;
|
|
69
|
+
const projectMatrix = new Matrix4().perspective(fov * Math.PI / 180, aspect, near, far, clipMode === 1);
|
|
70
|
+
const viewMatrix = new Matrix4().compose(new Vector3(...position), new Quaternion(), new Vector3(1, 1, 1)).invert();
|
|
71
|
+
const vpMatrix = new Matrix4().multiplyMatrices(projectMatrix, viewMatrix);
|
|
72
|
+
const inverseVpMatrix = vpMatrix.clone().invert();
|
|
73
|
+
this.cameraParam.position = new Vector3(...position);
|
|
74
|
+
this.cameraParam.rotation = new Euler().toDegreeVector3();
|
|
75
|
+
this.cameraParam.focusPosition = new Vector3(position[0], position[1], this.cameraParam.position.z > 0 ? 0 : this.cameraParam.position.z * 2);
|
|
76
|
+
this.cameraParam.focusPosition.applyEuler(new Euler().setFromDegreeVector3(this.cameraParam.rotation.clone()), this.cameraParam.position);
|
|
77
|
+
this.cameraParam.focusRotation = new Euler().toDegreeVector3();
|
|
78
|
+
this.cameraParam.viewProjectionMatrix = vpMatrix;
|
|
79
|
+
this.cameraParam.inverseViewProjectionMatrix = inverseVpMatrix;
|
|
80
|
+
}
|
|
81
|
+
initInteractionPlane(position, rotation) {
|
|
82
|
+
position ??= this.cameraParam.focusPosition;
|
|
83
|
+
rotation ??= new Euler().setFromDegreeVector3(this.cameraParam.rotation);
|
|
84
|
+
const normal = new Vector3(0, 0, 1).applyEuler(rotation);
|
|
85
|
+
this.plane.setFromNormalAndCoplanarPoint(position, normal);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* @description 根据视图空间大小获取空间大小
|
|
89
|
+
* @param viewSize 视图空间大小
|
|
90
|
+
* @param isWorld 是否依赖于世界坐标系 - 世界坐标系下,以当前相机锁定平面为基准进行运算;本地坐标系下,以元素坐标系进行运算[用于决定交互平面]
|
|
91
|
+
* @param targetItem 目标元素
|
|
92
|
+
*/
|
|
93
|
+
getWorldSizeByViewSize(viewSize, isWorld = true, itemId) {
|
|
94
|
+
// 交互平面需要旋转与相机相同角度
|
|
95
|
+
const rotation = isWorld || !itemId
|
|
96
|
+
? new Euler().setFromDegreeVector3(this.cameraParam.rotation)
|
|
97
|
+
: new Euler().setFromQuaternion(new Quaternion().setFromRotationMatrix(this.pageDataUtils.getItemTransformById(itemId).matrix));
|
|
98
|
+
const position = itemId
|
|
99
|
+
? new Vector3().setFromMatrixPosition(this.pageDataUtils.getItemTransformById(itemId).matrix)
|
|
100
|
+
: this.cameraParam.focusPosition;
|
|
101
|
+
const viewportScale = SDK.config.mode === 'editor' ? 1 : this.viewportParam.scale;
|
|
102
|
+
viewSize.multiply(viewportScale);
|
|
103
|
+
const focusViewPoint = this.projectPoint(position.clone());
|
|
104
|
+
const nextPoint = this.getWorldPositionByViewPoint(focusViewPoint.add(viewSize));
|
|
105
|
+
if (!nextPoint) {
|
|
106
|
+
console.warn('size is out of bounds.');
|
|
107
|
+
return new Vector3(0, 0, 0);
|
|
108
|
+
}
|
|
109
|
+
const resultRotation = new Euler().setFromQuaternion(new Quaternion().setFromEuler(rotation).invert());
|
|
110
|
+
const resultPosition = isWorld || !itemId ? nextPoint.applyEuler(resultRotation, position) : nextPoint;
|
|
111
|
+
const worldSize = resultPosition.subtract(position);
|
|
112
|
+
return worldSize;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* @description 获取 template 模式下视图与场景的缩放比 (viewportWidth / sceneWidth)
|
|
116
|
+
* 场景未就绪时回退到 1,并打印一次 warning
|
|
117
|
+
*/
|
|
118
|
+
getTemplateViewScale() {
|
|
119
|
+
const sceneSize = this.pageDataUtils.getSceneSize();
|
|
120
|
+
if (!sceneSize) {
|
|
121
|
+
console.warn('Scene is not existed, view scale will fallback to 1.');
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
return this.viewportParam.width / sceneSize.x;
|
|
125
|
+
}
|
|
126
|
+
getViewSizeByPixelSize(pixelSize) {
|
|
127
|
+
const { scale } = this.viewportParam;
|
|
128
|
+
const resultScale = SDK.config.mode === 'template' ? this.getTemplateViewScale() : scale;
|
|
129
|
+
return pixelSize.clone().multiply(resultScale);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* @description 对空间坐标进行投影
|
|
133
|
+
* @param point 空间坐标
|
|
134
|
+
* @param isGestureHandlerView 是否投影到辅助视图下 - 辅助试图与实际视图大小可能会有区别
|
|
135
|
+
*/
|
|
136
|
+
projectPoint(point) {
|
|
137
|
+
const { width, height, scale } = this.viewportParam;
|
|
138
|
+
const { viewProjectionMatrix } = this.cameraParam;
|
|
139
|
+
const viewportScale = SDK.config.mode === 'editor' ? 1 : scale;
|
|
140
|
+
const viewPoint = new Vector2().copyFrom(point.applyMatrix(viewProjectionMatrix).toVector2()).toViewCoordinate(width * viewportScale, height * viewportScale);
|
|
141
|
+
return viewPoint;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* @description 通过屏幕坐标获取世界坐标
|
|
145
|
+
* @param point 屏幕坐标
|
|
146
|
+
* @returns 世界坐标
|
|
147
|
+
*/
|
|
148
|
+
getWorldPositionByViewPoint(point) {
|
|
149
|
+
const { position, inverseViewProjectionMatrix: inverseViewProjectMatrix } = this.cameraParam;
|
|
150
|
+
// 刷新射线碰撞器
|
|
151
|
+
const coords = this.getNDCPositionByScreenPosition(point);
|
|
152
|
+
const rayCaster = new RayCaster().setFromCamera(coords, { position, inverseViewProjectMatrix });
|
|
153
|
+
// 计算射线碰撞结果
|
|
154
|
+
const result = rayCaster.rayCastPlane(this.plane);
|
|
155
|
+
return result?.point;
|
|
156
|
+
}
|
|
157
|
+
getNDCPositionByScreenPosition(point) {
|
|
158
|
+
const { width, height, scale } = this.viewportParam;
|
|
159
|
+
const resultScale = SDK.config.mode === 'editor' ? 1 : scale;
|
|
160
|
+
return new Vector2(point.x / (width * resultScale) * 2 - 1, 1 - point.y / (height * resultScale) * 2);
|
|
161
|
+
}
|
|
162
|
+
getNDCSizeByPixelSize(pixelSize) {
|
|
163
|
+
const { width, height } = this.viewportParam;
|
|
164
|
+
return new Vector2((pixelSize.x / width) * 2, -(pixelSize.y / height) * 2);
|
|
165
|
+
}
|
|
166
|
+
getViewPositionWWithOutTransform(point) {
|
|
167
|
+
const { scale } = this.viewportParam;
|
|
168
|
+
const resultScale = SDK.config.mode === 'editor' ? 1 : scale;
|
|
169
|
+
return point.divide(resultScale);
|
|
170
|
+
}
|
|
171
|
+
getViewPositionByPixelPoint(point) {
|
|
172
|
+
const { width, height, scale, translation } = this.viewportParam;
|
|
173
|
+
if (SDK.config.mode === 'template') {
|
|
174
|
+
const viewScale = this.getTemplateViewScale();
|
|
175
|
+
// 注意:必须 clone,避免修改入参
|
|
176
|
+
return point.clone().multiply(viewScale * scale);
|
|
177
|
+
}
|
|
178
|
+
return point.clone().scaleByCenter(new Vector2(scale, scale), new Vector2(width / 2, height / 2)).add(translation);
|
|
179
|
+
}
|
|
180
|
+
getPixelPositionByViewPoint(point) {
|
|
181
|
+
const { width, height, scale, translation } = this.viewportParam;
|
|
182
|
+
if (SDK.config.mode === 'template') {
|
|
183
|
+
const viewScale = this.getTemplateViewScale();
|
|
184
|
+
// 注意:必须 clone,避免修改入参
|
|
185
|
+
return point.clone().divide(viewScale * scale);
|
|
186
|
+
}
|
|
187
|
+
return point.clone().subtract(translation.clone()).scaleByCenter(new Vector2(1 / scale, 1 / scale), new Vector2(width / 2, height / 2));
|
|
188
|
+
}
|
|
189
|
+
getPixelSizeByWorldSize(worldSize) {
|
|
190
|
+
const startPoint = this.projectPoint(new Vector3());
|
|
191
|
+
const targetPoint = this.projectPoint(worldSize);
|
|
192
|
+
const pixelSize = targetPoint.subtract(startPoint);
|
|
193
|
+
pixelSize.abs();
|
|
194
|
+
return pixelSize;
|
|
195
|
+
}
|
|
196
|
+
getPixelSizeByNDCSize(ndcSize) {
|
|
197
|
+
const { width, height } = this.viewportParam;
|
|
198
|
+
return new Vector2(ndcSize.x / 2 * width, -ndcSize.y / 2 * height);
|
|
199
|
+
}
|
|
200
|
+
getPixelSizeByViewSize(viewSize) {
|
|
201
|
+
const { scale } = this.viewportParam;
|
|
202
|
+
const resultScale = SDK.config.mode === 'template' ? this.getTemplateViewScale() * scale : scale;
|
|
203
|
+
return viewSize.clone().divide(resultScale);
|
|
204
|
+
}
|
|
205
|
+
}
|