@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.
@@ -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;
@@ -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[];
@@ -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? : travelerClipArea;
23
+ travelerClipArea?: travelerClipArea;
29
24
  }
30
25
 
31
26
  export interface OverlayConfig extends BaseConfig {
@@ -1,22 +1,27 @@
1
- //Bitmask Flags
2
- export const DIRTY_NONE = 0;
3
- export const DIRTY_RECT = 1 << 0; // 1: x, y, width, height (00000001)
4
- export const DIRTY_STYLE = 1 << 1; // 2: style (00000010)
5
- export const DIRTY_ZINDEX = 1 << 2; // 4: zIndex (00000100)
6
- export const DIRTY_STRUCTURE = 1 << 3; // 8: childNode add / delete (00001000)
7
- export const DIRTY_CONTENT = 1 << 4; // 16: text content changed (00010000)
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 SYSTEM_LAYER = 1 << 1;
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);
@@ -1,3 +1,5 @@
1
1
  export * from './config';
2
2
  export * from './common';
3
- export * from './flags';
3
+ export * from './attributes';
4
+ export * from './flags';
5
+ export * from '@mirage-engine/dom-tracker';