@mirage-engine/core 0.2.1 → 0.3.0
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/CHANGELOG.md +20 -0
- package/dist/mirage-engine.js +1443 -503
- package/dist/mirage-engine.umd.js +142 -72
- package/dist/src/animation/Animator.d.ts +6 -0
- package/dist/src/core/Engine.d.ts +5 -0
- package/dist/src/core/Syncer.d.ts +5 -16
- package/dist/src/dom/Extractor.d.ts +1 -2
- package/dist/src/renderer/Renderer.d.ts +14 -8
- package/dist/src/store/MeshRegistry.d.ts +9 -0
- package/dist/src/store/TextureLifecycleManager.d.ts +16 -0
- package/dist/src/types/animate.d.ts +19 -0
- package/dist/src/types/attributes.d.ts +63 -0
- package/dist/src/types/common.d.ts +9 -0
- package/dist/src/types/config.d.ts +2 -8
- package/dist/src/types/flags.d.ts +8 -7
- package/dist/src/types/index.d.ts +2 -0
- package/package.json +3 -2
- package/src/animation/Animator.ts +108 -0
- package/src/core/Engine.ts +69 -5
- package/src/core/Syncer.ts +49 -190
- package/src/dom/Extractor.ts +498 -64
- package/src/env.d.ts +4 -0
- package/src/renderer/Renderer.ts +430 -133
- package/src/store/MeshRegistry.ts +32 -0
- package/src/store/TextureLifecycleManager.ts +126 -0
- package/src/types/animate.ts +26 -0
- package/src/types/attributes.ts +57 -0
- package/src/types/common.ts +6 -0
- package/src/types/config.ts +5 -10
- package/src/types/flags.ts +20 -15
- package/src/types/index.ts +3 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
|
|
3
|
+
export class MeshRegistry {
|
|
4
|
+
private store: WeakMap<HTMLElement, THREE.Mesh>;
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this.store = new WeakMap();
|
|
8
|
+
}
|
|
9
|
+
public register(element: HTMLElement, mesh: THREE.Mesh): void {
|
|
10
|
+
this.store.set(element, mesh);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public get(element: HTMLElement): THREE.Mesh | undefined {
|
|
14
|
+
return this.store.get(element);
|
|
15
|
+
}
|
|
16
|
+
// public register(element: HTMLElement, meshGroup: THREE.Group): void {
|
|
17
|
+
// this.store.set(element, meshGroup);
|
|
18
|
+
// }
|
|
19
|
+
|
|
20
|
+
// public get(element: HTMLElement): THREE.Group | undefined {
|
|
21
|
+
// return this.store.get(element);
|
|
22
|
+
// }
|
|
23
|
+
|
|
24
|
+
public has(element: HTMLElement): boolean {
|
|
25
|
+
return this.store.has(element);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Optional
|
|
29
|
+
public remove(element: HTMLElement): void {
|
|
30
|
+
this.store.delete(element);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
|
|
3
|
+
export type TextureReadyCallback = (
|
|
4
|
+
element: HTMLElement,
|
|
5
|
+
texture: THREE.Texture | null,
|
|
6
|
+
) => void;
|
|
7
|
+
|
|
8
|
+
export class TextureLifecycleManager {
|
|
9
|
+
private observer: IntersectionObserver;
|
|
10
|
+
private textures: WeakMap<HTMLElement, THREE.Texture> = new WeakMap();
|
|
11
|
+
private loadStatus: WeakMap<HTMLElement, boolean> = new WeakMap();
|
|
12
|
+
private elementUrls: WeakMap<HTMLElement, string> = new WeakMap();
|
|
13
|
+
private onUpdate: TextureReadyCallback;
|
|
14
|
+
|
|
15
|
+
constructor(onUpdate: TextureReadyCallback) {
|
|
16
|
+
this.onUpdate = onUpdate;
|
|
17
|
+
this.observer = new IntersectionObserver(
|
|
18
|
+
(entries) => {
|
|
19
|
+
for (const entry of entries) {
|
|
20
|
+
const el = entry.target as HTMLElement;
|
|
21
|
+
if (entry.isIntersecting) {
|
|
22
|
+
this.loadTexture(el);
|
|
23
|
+
} else {
|
|
24
|
+
this.disposeTexture(el);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{ rootMargin: "300px" },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public register(element: HTMLElement, url: string) {
|
|
33
|
+
if (element.nodeType !== Node.ELEMENT_NODE) return;
|
|
34
|
+
const currentUrl = this.elementUrls.get(element);
|
|
35
|
+
if (currentUrl !== url) {
|
|
36
|
+
this.elementUrls.set(element, url);
|
|
37
|
+
// Restart observation to trigger intersecting check
|
|
38
|
+
this.observer.unobserve(element);
|
|
39
|
+
this.observer.observe(element);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public unregister(element: HTMLElement) {
|
|
44
|
+
if (element.nodeType === Node.ELEMENT_NODE) {
|
|
45
|
+
this.observer.unobserve(element);
|
|
46
|
+
}
|
|
47
|
+
this.disposeTexture(element);
|
|
48
|
+
this.elementUrls.delete(element);
|
|
49
|
+
this.loadStatus.delete(element);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private async loadTexture(element: HTMLElement) {
|
|
53
|
+
if (this.loadStatus.get(element) || this.textures.has(element)) return;
|
|
54
|
+
|
|
55
|
+
const url = this.elementUrls.get(element);
|
|
56
|
+
if (!url) return;
|
|
57
|
+
|
|
58
|
+
this.loadStatus.set(element, true);
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
let textureImage: ImageBitmap | HTMLImageElement;
|
|
62
|
+
|
|
63
|
+
if (url.startsWith("data:image/svg+xml")) {
|
|
64
|
+
textureImage = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
65
|
+
const img = new Image();
|
|
66
|
+
img.onload = () => resolve(img);
|
|
67
|
+
img.onerror = reject;
|
|
68
|
+
img.src = url;
|
|
69
|
+
});
|
|
70
|
+
} else {
|
|
71
|
+
const response = await fetch(url);
|
|
72
|
+
const blob = await response.blob();
|
|
73
|
+
textureImage = await createImageBitmap(blob, { imageOrientation: "flipY" });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check if URL changed or element was unregistered while decoding
|
|
77
|
+
if (this.elementUrls.get(element) !== url) {
|
|
78
|
+
if ('close' in textureImage) textureImage.close();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const texture = new THREE.Texture(textureImage);
|
|
83
|
+
// createImageBitmap already flips Y, but HTMLImageElement relies on THREE.js flipY
|
|
84
|
+
if (!(textureImage instanceof HTMLImageElement)) {
|
|
85
|
+
texture.flipY = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Inherit CanvasTexture defaults or set them explicitly if needed
|
|
89
|
+
texture.colorSpace = THREE.LinearSRGBColorSpace; // or adjust according to your pipeline
|
|
90
|
+
texture.needsUpdate = true;
|
|
91
|
+
|
|
92
|
+
this.textures.set(element, texture);
|
|
93
|
+
this.onUpdate(element, texture);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.warn("[MirageEngine] Failed to load texture:", url, e);
|
|
96
|
+
} finally {
|
|
97
|
+
// Re-check url, if it changed, we should probably trigger loadTexture again,
|
|
98
|
+
// but IntersectionObserver will handle it if it's still intersecting.
|
|
99
|
+
if (this.elementUrls.has(element)) {
|
|
100
|
+
this.loadStatus.set(element, false);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private disposeTexture(element: HTMLElement) {
|
|
106
|
+
const texture = this.textures.get(element);
|
|
107
|
+
if (texture) {
|
|
108
|
+
texture.dispose();
|
|
109
|
+
this.textures.delete(element);
|
|
110
|
+
this.onUpdate(element, null);
|
|
111
|
+
}
|
|
112
|
+
// Also cancel loading state if we had one
|
|
113
|
+
this.loadStatus.set(element, false);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public get(element: HTMLElement) {
|
|
117
|
+
return this.textures.get(element);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public disposeAll() {
|
|
121
|
+
this.observer.disconnect();
|
|
122
|
+
// Assuming WeakMap handles GC of remaining textures, but in WebGL we must explicitly delete.
|
|
123
|
+
// However, WeakMap is not iterable, so we can't easily iterate and delete.
|
|
124
|
+
// The renderer should unregister nodes upon pendingDeletions.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface StyleData {
|
|
2
|
+
// 1. Transform
|
|
3
|
+
x?: number;
|
|
4
|
+
y?: number;
|
|
5
|
+
z?: number;
|
|
6
|
+
scaleX?: number;
|
|
7
|
+
scaleY?: number;
|
|
8
|
+
scaleZ?: number;
|
|
9
|
+
rotateX?: number;
|
|
10
|
+
rotateY?: number;
|
|
11
|
+
rotateZ?: number;
|
|
12
|
+
|
|
13
|
+
// for future use
|
|
14
|
+
matrix?: Float32Array;
|
|
15
|
+
|
|
16
|
+
// 2. Material / Appearance
|
|
17
|
+
opacity?: number;
|
|
18
|
+
backgroundColor?: [number, number, number];
|
|
19
|
+
backgroundImage?: string;
|
|
20
|
+
borderRadius?: number | [number, number, number, number];
|
|
21
|
+
|
|
22
|
+
// 3. Layout / Depth
|
|
23
|
+
width?: number;
|
|
24
|
+
height?: number;
|
|
25
|
+
zIndex?: number;
|
|
26
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const filter_values = {
|
|
2
|
+
INCLUDE_TREE: "include-tree",
|
|
3
|
+
EXCLUDE_TREE: "exclude-tree",
|
|
4
|
+
INCLUDE_SELF: "include-self",
|
|
5
|
+
EXCLUDE_SELF: "exclude-self",
|
|
6
|
+
END: "end",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const ATTR_DOM = {
|
|
10
|
+
NAME: "data-mirage-dom",
|
|
11
|
+
KEY: "mirageDom",
|
|
12
|
+
VALUES: {
|
|
13
|
+
HIDE: "hide",
|
|
14
|
+
},
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export const TRAVEL_VALUES = {
|
|
18
|
+
TRAVELER: "traveler",
|
|
19
|
+
NATIVE: "native",
|
|
20
|
+
CAPTURE_1: "1",
|
|
21
|
+
CAPTURE_2: "2",
|
|
22
|
+
CAPTURE_3: "3",
|
|
23
|
+
CAPTURE_4: "4",
|
|
24
|
+
CAPTURE_5: "5",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
export const ATTR_TRAVEL = {
|
|
28
|
+
NAME: "data-mirage-travel",
|
|
29
|
+
KEY: "mirageTravel",
|
|
30
|
+
VALUES: TRAVEL_VALUES,
|
|
31
|
+
MAX_LAYERS: Object.keys(TRAVEL_VALUES).length - 1,
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
export const ATTR_FILTER = {
|
|
35
|
+
NAME: "data-mirage-filter",
|
|
36
|
+
KEY: "mirageFilter",
|
|
37
|
+
VALUES: filter_values,
|
|
38
|
+
} as const;
|
|
39
|
+
|
|
40
|
+
export const ATTR_SELECT = {
|
|
41
|
+
NAME: "data-mirage-select",
|
|
42
|
+
KEY: "mirageSelect",
|
|
43
|
+
VALUES: filter_values,
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export const ATTR_SHADER = {
|
|
47
|
+
NAME: "data-mirage-shader",
|
|
48
|
+
KEY: "mirageShader",
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
export const ATTR_SANDWICH = {
|
|
52
|
+
NAME: "data-mirage-sandwich",
|
|
53
|
+
KEY: "mirageSandwich",
|
|
54
|
+
VALUES: {
|
|
55
|
+
FRONT: "front",
|
|
56
|
+
},
|
|
57
|
+
} as const;
|
package/src/types/common.ts
CHANGED
|
@@ -20,12 +20,18 @@ export interface SceneNode {
|
|
|
20
20
|
|
|
21
21
|
textContent?: string;
|
|
22
22
|
textStyles?: TextStyles;
|
|
23
|
+
textLines?: { text: string; rect: NodeRect }[];
|
|
23
24
|
|
|
24
25
|
dirtyMask: number;
|
|
25
26
|
// TODO: SceneNode의 sort 로직 추가(아마도)
|
|
26
27
|
|
|
27
28
|
visibility: Visibility;
|
|
28
29
|
isTraveler: boolean;
|
|
30
|
+
captureLayer: number;
|
|
31
|
+
nativeLayer?: number;
|
|
32
|
+
nativeStyles?: BoxStyles | TextStyles;
|
|
33
|
+
nativeRect?: NodeRect;
|
|
34
|
+
isFixed: boolean;
|
|
29
35
|
shaderHooks?: ShaderHooks;
|
|
30
36
|
|
|
31
37
|
children: SceneNode[];
|
package/src/types/config.ts
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
export type Quality = "low" | "medium" | "high" | number;
|
|
2
|
+
export type LayerTarget = "base" | "selected"
|
|
2
3
|
export type MirageMode = "overlay" | "duplicate";
|
|
3
4
|
export type PxUnit = `${number}px`;
|
|
4
5
|
export type PercentUnit = `${number}%`;
|
|
5
6
|
export type travelerClipArea = PxUnit | PercentUnit | number;
|
|
6
|
-
export interface FilterConfig {
|
|
7
|
-
includeTree?: string[];
|
|
8
|
-
excludeTree?: string[];
|
|
9
|
-
includeSelf?: string[];
|
|
10
|
-
excludeSelf?: string[];
|
|
11
|
-
end?: string[];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
7
|
export interface ResizeConfig {
|
|
15
8
|
delay?: number;
|
|
16
9
|
onStart?: () => void;
|
|
17
10
|
onEnd?: () => void;
|
|
18
11
|
}
|
|
19
12
|
|
|
13
|
+
|
|
14
|
+
|
|
20
15
|
interface BaseConfig {
|
|
21
16
|
debug?: boolean;
|
|
17
|
+
layer?: number | LayerTarget;
|
|
22
18
|
quality?: Quality;
|
|
23
19
|
style?: {
|
|
24
20
|
zIndex?: string;
|
|
25
21
|
};
|
|
26
|
-
filter?: FilterConfig;
|
|
27
22
|
resizeDebounce?: boolean | ResizeConfig;
|
|
28
|
-
travelerClipArea
|
|
23
|
+
travelerClipArea?: travelerClipArea;
|
|
29
24
|
}
|
|
30
25
|
|
|
31
26
|
export interface OverlayConfig extends BaseConfig {
|
package/src/types/flags.ts
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { ATTR_FILTER } from "./attributes";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
DIRTY_NONE,
|
|
5
|
+
DIRTY_RECT,
|
|
6
|
+
DIRTY_STYLE,
|
|
7
|
+
DIRTY_ZINDEX,
|
|
8
|
+
DIRTY_STRUCTURE,
|
|
9
|
+
DIRTY_CONTENT,
|
|
10
|
+
} from "@mirage-engine/dom-tracker";
|
|
8
11
|
|
|
9
12
|
// Filtering Flag
|
|
10
13
|
export const USER_LAYER = 1 << 0;
|
|
11
|
-
export const
|
|
14
|
+
export const SELECT_LAYER = 1 << 1;
|
|
12
15
|
export const EXCLUDED = 0;
|
|
13
16
|
|
|
17
|
+
// Three.js Layer Channels (0 ~ 31)
|
|
18
|
+
export const THREE_LAYERS = {
|
|
19
|
+
BASE: 0,
|
|
20
|
+
SELECTED: 1,
|
|
21
|
+
getCaptureLayer: (layerNum: number) => 31 - layerNum,
|
|
22
|
+
HIDDEN: 31,
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
14
25
|
export type Visibility = 0 | 1 | 2 | 3;
|
|
15
26
|
|
|
16
|
-
export const ALLOWED_FILTERS =
|
|
17
|
-
"include-tree",
|
|
18
|
-
"exclude-tree",
|
|
19
|
-
"include-self",
|
|
20
|
-
"exclude-self",
|
|
21
|
-
"end",
|
|
22
|
-
];
|
|
27
|
+
export const ALLOWED_FILTERS = Object.values(ATTR_FILTER.VALUES);
|
package/src/types/index.ts
CHANGED