mirage-engine 0.2.4 → 0.2.6

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/README.md CHANGED
@@ -3,9 +3,11 @@
3
3
  > **An engine that mirrors HTML DOM elements to a WebGL scene in real-time.**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/mirage-engine.svg?style=flat-square)](https://www.npmjs.com/package/mirage-engine)
6
+ [![NPM Downloads](https://img.shields.io/npm/dm/mirage-engine.svg)](https://www.npmjs.org/package/mirage-engine)
6
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
7
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
8
9
 
10
+
9
11
  MirageEngine directly mirrors HTML DOM elements to WebGL objects. It observes DOM mutations and synchronizes position, style, and content in real-time, allowing standard HTML elements to exist within a WebGL context.
10
12
 
11
13
  ## Installation
@@ -19,8 +21,11 @@ npm install mirage-engine
19
21
  ```ts
20
22
  import { Mirage } from 'mirage-engine';
21
23
 
22
- const engine = new Mirage("#target");
23
- engine.start();
24
+ const target = document.querySelector("#target") as HTMLElement;
25
+
26
+ const mirage = new Mirage(target);
27
+
28
+ mirage.start();
24
29
  ```
25
30
 
26
31
  **License | MIT © dltldn333**
@@ -1,221 +1,297 @@
1
- var b = Object.defineProperty;
2
- var D = (i, e, t) => e in i ? b(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
3
- var o = (i, e, t) => (D(i, typeof e != "symbol" ? e + "" : e, t), t);
4
- import * as c from "three";
5
- const g = 0, T = 1, y = 2, C = 4, w = 8, x = 16;
6
- function N(i, e, t, s) {
7
- const n = document.createElement("canvas"), r = n.getContext("2d");
8
- if (!r)
1
+ var L = Object.defineProperty;
2
+ var z = (r, t, e) => t in r ? L(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var a = (r, t, e) => (z(r, typeof t != "symbol" ? t + "" : t, e), e);
4
+ import * as g from "three";
5
+ const x = 0, R = 1, v = 2, I = 4, C = 8, S = 16;
6
+ function N(r, t, e) {
7
+ const i = t.split(`
8
+ `), s = [];
9
+ return i.forEach((n) => {
10
+ const o = n.split(" ");
11
+ let c = o[0];
12
+ for (let d = 1; d < o.length; d++) {
13
+ const l = o[d];
14
+ r.measureText(c + " " + l).width < e ? c += " " + l : (s.push(c), c = l);
15
+ }
16
+ s.push(c);
17
+ }), s;
18
+ }
19
+ function X(r, t, e, i, s = 2) {
20
+ const n = document.createElement("canvas"), o = n.getContext("2d");
21
+ if (!o)
9
22
  throw new Error("[Mirage] Failed to create canvas context");
10
- const a = window.devicePixelRatio || 2;
11
- n.width = t * a, n.height = s * a, r.scale(a, a), r.font = e.font, r.fillStyle = e.color, r.textAlign = e.textAlign, r.direction = e.direction, r.textBaseline = "middle";
12
- let l = 0;
13
- e.textAlign === "center" && (l = t / 2), e.textAlign === "right" && (l = t);
14
- const m = s / 2;
15
- r.fillText(i, l, m);
16
- const h = new c.CanvasTexture(n);
17
- return h.colorSpace = c.SRGBColorSpace, h.minFilter = c.LinearFilter, h.needsUpdate = !0, h;
23
+ const d = (window.devicePixelRatio || 1) * s;
24
+ n.width = e * d, n.height = i * d, o.scale(d, d), o.font = t.font, o.fillStyle = t.color, o.textBaseline = "top", o.globalAlpha = 1;
25
+ const l = N(o, r, e), f = t.lineHeight;
26
+ l.forEach((y, u) => {
27
+ const p = u * f + 2;
28
+ let m = 0;
29
+ t.textAlign === "center" ? m = e / 2 : t.textAlign === "right" && (m = e), o.textAlign = t.textAlign, o.fillText(y, m, p);
30
+ });
31
+ const h = new g.CanvasTexture(n);
32
+ return h.colorSpace = g.SRGBColorSpace, h.minFilter = g.LinearFilter, h.magFilter = g.LinearFilter, h.needsUpdate = !0, h;
18
33
  }
19
- class R {
20
- constructor() {
21
- o(this, "canvas");
22
- o(this, "scene");
23
- o(this, "camera");
24
- o(this, "renderer");
25
- o(this, "renderOrder", 0);
26
- o(this, "meshMap", /* @__PURE__ */ new Map());
27
- this.canvas = document.createElement("canvas"), this.scene = new c.Scene();
28
- const e = window.innerWidth, t = window.innerHeight;
29
- this.camera = new c.OrthographicCamera(
30
- e / -2,
31
- e / 2,
32
- t / 2,
33
- t / -2,
34
+ class _ {
35
+ constructor(t, e, i) {
36
+ a(this, "canvas");
37
+ a(this, "scene");
38
+ a(this, "camera");
39
+ a(this, "renderer");
40
+ a(this, "renderOrder", 0);
41
+ a(this, "textQualityFactor", 2);
42
+ a(this, "mode", "overlay");
43
+ a(this, "customZIndex", "9999");
44
+ a(this, "target");
45
+ a(this, "mountContainer");
46
+ a(this, "targetRect");
47
+ a(this, "meshMap", /* @__PURE__ */ new Map());
48
+ var o;
49
+ this.target = t, this.mountContainer = i, this.mode = e.mode ?? "overlay", (o = e.style) != null && o.zIndex && (this.customZIndex = e.style.zIndex), this.canvas = document.createElement("canvas"), this.scene = new g.Scene(), this.targetRect = this.target.getBoundingClientRect();
50
+ const s = this.targetRect.width, n = this.targetRect.height;
51
+ this.camera = new g.OrthographicCamera(
52
+ s / -2,
53
+ s / 2,
54
+ n / 2,
55
+ n / -2,
34
56
  1,
35
57
  1e3
36
- ), this.camera.position.z = 100, this.renderer = new c.WebGLRenderer({
58
+ ), this.camera.position.z = 100, this.renderer = new g.WebGLRenderer({
37
59
  canvas: this.canvas,
38
- alpha: !0
39
- }), this.renderer.setSize(e, t);
60
+ alpha: !0,
61
+ antialias: !0
62
+ }), this.renderer.setPixelRatio(window.devicePixelRatio), this.renderer.setSize(s, n), this.applyTextQuality(e.textQuality ?? "medium");
63
+ }
64
+ applyTextQuality(t) {
65
+ if (typeof t == "number") {
66
+ this.textQualityFactor = Math.max(0.1, t);
67
+ return;
68
+ }
69
+ switch (t) {
70
+ case "low":
71
+ this.textQualityFactor = 1;
72
+ break;
73
+ case "high":
74
+ this.textQualityFactor = 4;
75
+ break;
76
+ case "medium":
77
+ default:
78
+ this.textQualityFactor = 2;
79
+ break;
80
+ }
40
81
  }
41
- mount(e) {
42
- e.appendChild(this.canvas);
82
+ mount() {
83
+ this.mountContainer.appendChild(this.canvas), this.canvas.style.zIndex = this.customZIndex, this.canvas.style.pointerEvents = this.mode === "overlay" ? "none" : "auto", this.updateCanvasLayout();
84
+ }
85
+ updateCanvasLayout() {
86
+ this.canvas.style.width = `${this.targetRect.width}px`, this.canvas.style.height = `${this.targetRect.height}px`, this.mode === "duplicate" ? (this.canvas.style.position = "", this.canvas.style.top = "", this.canvas.style.left = "", this.canvas.style.display = "block") : (this.canvas.style.position = "absolute", this.canvas.style.top = `${this.target.offsetTop}px`, this.canvas.style.left = `${this.target.offsetLeft}px`, this.canvas.style.display = "block");
43
87
  }
44
88
  dispose() {
45
- try {
46
- this.renderer.dispose();
47
- } catch {
48
- }
49
- this.canvas.parentElement && this.canvas.parentElement.removeChild(this.canvas);
89
+ this.renderer.dispose(), this.canvas.remove();
50
90
  }
51
- setSize(e, t) {
52
- this.renderer.setSize(e, t), this.camera.left = e / -2, this.camera.right = e / 2, this.camera.top = t / 2, this.camera.bottom = t / -2, this.camera.updateProjectionMatrix();
91
+ setSize(t, e) {
92
+ this.renderer.setSize(t, e), this.camera.left = t / -2, this.camera.right = t / 2, this.camera.top = e / 2, this.camera.bottom = e / -2, this.camera.updateProjectionMatrix();
53
93
  }
54
- syncScene(e) {
55
- this.renderOrder = 0;
56
- const t = /* @__PURE__ */ new Set();
57
- this.reconcileNode(e, t);
58
- for (const [s, n] of this.meshMap.entries())
59
- t.has(s) || (this.scene.remove(n), n.geometry.dispose(), n.material instanceof c.Material && n.material.dispose(), this.meshMap.delete(s));
94
+ syncScene(t) {
95
+ const e = this.target.getBoundingClientRect(), i = Math.abs(e.width - this.targetRect.width) > 0.1 || Math.abs(e.height - this.targetRect.height) > 0.1, s = this.mode === "overlay" && (Math.abs(e.top - this.targetRect.top) > 0.1 || Math.abs(e.left - this.targetRect.left) > 0.1);
96
+ i ? (this.targetRect = e, this.renderer.setSize(this.targetRect.width, this.targetRect.height), this.camera.left = this.targetRect.width / -2, this.camera.right = this.targetRect.width / 2, this.camera.top = this.targetRect.height / 2, this.camera.bottom = this.targetRect.height / -2, this.camera.updateProjectionMatrix(), this.updateCanvasLayout()) : s ? (this.targetRect = e, this.updateCanvasLayout()) : this.targetRect = e, this.renderOrder = 0;
97
+ const n = /* @__PURE__ */ new Set();
98
+ this.reconcileNode(t, n);
99
+ for (const [o, c] of this.meshMap.entries())
100
+ n.has(o) || (this.scene.remove(c), c.geometry.dispose(), c.material instanceof g.Material && c.material.dispose(), this.meshMap.delete(o));
60
101
  }
61
- reconcileNode(e, t) {
62
- var s, n;
63
- if (e.type === "BOX") {
64
- t.add(e.element);
65
- let r = this.meshMap.get(e.element);
66
- if (!r) {
67
- const a = new c.PlaneGeometry(1, 1), l = new c.MeshBasicMaterial({ transparent: !0 });
68
- r = new c.Mesh(a, l), this.scene.add(r), this.meshMap.set(e.element, r);
69
- }
70
- this.updateMeshProperties(r, e);
71
- for (const a of e.children)
72
- this.reconcileNode(a, t);
102
+ reconcileNode(t, e) {
103
+ e.add(t.element);
104
+ let i = this.meshMap.get(t.element);
105
+ if (!i) {
106
+ const s = new g.PlaneGeometry(1, 1), n = new g.MeshBasicMaterial({ transparent: !0 });
107
+ i = new g.Mesh(s, n), t.type === "TEXT" && (i.name = "BG_MESH"), this.scene.add(i), this.meshMap.set(t.element, i);
73
108
  }
74
- if (e.type === "TEXT") {
75
- t.add(e.element);
76
- let r = this.meshMap.get(e.element);
77
- if (!r) {
78
- const d = new c.PlaneGeometry(1, 1), u = new c.MeshBasicMaterial({ transparent: !0 });
79
- r = new c.Mesh(d, u), r.name = "BG_MESH", this.scene.add(r), this.meshMap.set(e.element, r);
80
- }
81
- this.updateMeshProperties(r, e);
82
- let a = r.children.find(
83
- (d) => d.name === "TEXT_CHILD"
84
- );
85
- const l = JSON.stringify(e.textStyles), m = (s = a == null ? void 0 : a.userData) == null ? void 0 : s.styleHash, h = e.dirtyMask & x;
86
- if (!a || h || l !== m) {
87
- a && ((n = a.material.map) == null || n.dispose(), a.geometry.dispose(), r.remove(a));
88
- const d = N(
89
- e.textContent || "",
90
- e.textStyles,
91
- e.rect.width,
92
- e.rect.height
93
- ), u = new c.PlaneGeometry(1, 1), f = new c.MeshBasicMaterial({
94
- map: d,
95
- transparent: !0,
96
- side: c.FrontSide,
97
- color: 16777215,
98
- opacity: 1
99
- });
100
- a = new c.Mesh(u, f), a.name = "TEXT_CHILD", a.userData = { styleHash: l }, a.position.z = 5e-3, r.add(a);
101
- }
109
+ if (i.userData.domRect = t.rect, this.updateMeshProperties(i, t), t.type === "BOX")
110
+ for (const s of t.children)
111
+ this.reconcileNode(s, e);
112
+ else
113
+ t.type === "TEXT" && this.reconcileTextChild(i, t);
114
+ }
115
+ reconcileTextChild(t, e) {
116
+ var c, d;
117
+ let i = t.children.find(
118
+ (l) => l.name === "TEXT_CHILD"
119
+ );
120
+ const s = JSON.stringify(e.textStyles), n = (c = i == null ? void 0 : i.userData) == null ? void 0 : c.styleHash;
121
+ if (!i || e.dirtyMask & S || s !== n) {
122
+ i && ((d = i.material.map) == null || d.dispose(), i.geometry.dispose(), t.remove(i));
123
+ const l = X(
124
+ e.textContent || "",
125
+ e.textStyles,
126
+ e.rect.width,
127
+ e.rect.height,
128
+ this.textQualityFactor
129
+ ), f = new g.PlaneGeometry(1, 1), h = new g.MeshBasicMaterial({
130
+ map: l,
131
+ transparent: !0,
132
+ side: g.FrontSide,
133
+ color: 16777215
134
+ });
135
+ i = new g.Mesh(f, h), i.name = "TEXT_CHILD", i.userData = { styleHash: s }, t.add(i);
136
+ }
137
+ if (i) {
138
+ const l = t.userData.domRect, f = l.x + l.width / 2, h = l.y + l.height / 2, y = e.rect.x + e.rect.width / 2, u = e.rect.y + e.rect.height / 2, p = y - f, m = -(u - h);
139
+ i.position.set(p, m, 5e-3);
102
140
  }
103
141
  }
104
- updateMeshProperties(e, t) {
105
- const { rect: s, styles: n } = t, r = this.renderer.domElement.width, a = this.renderer.domElement.height;
106
- e.scale.set(s.width, s.height, 1);
107
- const l = 1e-3;
108
- this.renderOrder++, e.position.set(
109
- s.x - r / 2 + s.width / 2,
110
- -s.y + a / 2 - s.height / 2,
111
- n.zIndex + this.renderOrder * l
142
+ updateMeshProperties(t, e) {
143
+ const { rect: i, styles: s } = e, n = this.renderer.getPixelRatio(), o = this.renderer.domElement.width / n, c = this.renderer.domElement.height / n;
144
+ t.scale.set(i.width, i.height, 1);
145
+ const d = 1e-3;
146
+ this.renderOrder++;
147
+ const l = this.targetRect.left + window.scrollX, f = this.targetRect.top + window.scrollY, h = i.x - l, y = i.y - f;
148
+ t.position.set(
149
+ h - o / 2 + i.width / 2,
150
+ -y + c / 2 - i.height / 2,
151
+ s.zIndex + this.renderOrder * d
112
152
  );
113
- const m = e.material, h = n.backgroundColor;
114
- let p = h, d = 1;
115
- if (h === "transparent" || h === "rgba(0, 0, 0, 0)")
116
- p = "#ffffff", d = 0;
117
- else if (h.startsWith("rgba")) {
118
- const f = h.match(/[\d.]+/g);
119
- if (f && f.length >= 4) {
120
- const E = f[0], M = f[1], v = f[2];
121
- d = parseFloat(f[3]), p = `rgb(${E}, ${M}, ${v})`;
153
+ const u = t.material, p = s.backgroundColor;
154
+ let m = p, T = 1;
155
+ if (p === "transparent" || p === "rgba(0, 0, 0, 0)")
156
+ m = "#ffffff", T = 0;
157
+ else if (p.startsWith("rgba")) {
158
+ const w = p.match(/[\d.]+/g);
159
+ if (w && w.length >= 4) {
160
+ const M = w[0], D = w[1], F = w[2];
161
+ T = parseFloat(w[3]), m = `rgb(${M}, ${D}, ${F})`;
122
162
  }
123
163
  }
124
- const u = n.opacity * d;
125
- m.color.set(p), m.opacity = u, m.transparent = u < 1;
164
+ const b = s.opacity * T;
165
+ u.color.set(m), u.opacity = b, u.transparent = b < 1;
126
166
  }
127
167
  render() {
128
168
  this.renderer.render(this.scene, this.camera);
129
169
  }
130
170
  }
131
- function F(i) {
132
- var e;
133
- return i.nodeType === Node.TEXT_NODE && (((e = i.textContent) == null ? void 0 : e.trim().length) || 0) > 0;
134
- }
135
- function z(i) {
136
- const e = Array.from(i.childNodes);
137
- return e.length === 0 || e.some(
138
- (n) => n.nodeType === Node.ELEMENT_NODE
139
- ) ? !1 : e.some(F);
171
+ function k(r) {
172
+ const t = document.createRange();
173
+ t.selectNodeContents(r);
174
+ const e = t.getBoundingClientRect();
175
+ return {
176
+ left: e.left,
177
+ top: e.top,
178
+ width: e.width,
179
+ height: e.height
180
+ };
140
181
  }
141
- function L(i) {
142
- const e = parseFloat(i.fontSize);
143
- let t = parseFloat(i.lineHeight);
144
- isNaN(t) && (t = e * 1.2);
145
- let s = parseFloat(i.letterSpacing);
146
- return isNaN(s) && (s = 0), {
147
- font: `${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,
148
- color: i.color,
149
- textAlign: i.textAlign || "start",
182
+ function O(r) {
183
+ const t = parseFloat(r.fontSize);
184
+ let e = parseFloat(r.lineHeight);
185
+ isNaN(e) && (e = t * 1.2);
186
+ let i = parseFloat(r.letterSpacing);
187
+ return isNaN(i) && (i = 0), {
188
+ font: `${r.fontStyle} ${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,
189
+ color: r.color,
190
+ textAlign: r.textAlign || "start",
150
191
  textBaseline: "alphabetic",
151
- direction: i.direction || "inherit",
152
- lineHeight: t,
153
- letterSpacing: s
192
+ direction: r.direction || "inherit",
193
+ lineHeight: e,
194
+ letterSpacing: i
154
195
  };
155
196
  }
156
- function S(i, e = T | y | C | x | w) {
157
- const t = i.getBoundingClientRect(), s = window.getComputedStyle(i);
158
- if (t.width === 0 || t.height === 0 || s.display === "none")
197
+ function E(r, t = R | v | I | S | C) {
198
+ if (r.nodeType === Node.TEXT_NODE) {
199
+ const h = r;
200
+ if (!h.textContent || !h.textContent.trim())
201
+ return null;
202
+ const y = h.textContent.replace(/\s+/g, " ").trim();
203
+ if (y.length === 0)
204
+ return null;
205
+ const u = k(h);
206
+ if (u.width === 0 || u.height === 0)
207
+ return null;
208
+ const p = h.parentElement, m = p ? window.getComputedStyle(p) : null;
209
+ return m ? {
210
+ id: Math.random().toString(36).substring(2, 9),
211
+ type: "TEXT",
212
+ element: h,
213
+ rect: {
214
+ x: u.left + window.scrollX,
215
+ y: u.top + window.scrollY,
216
+ width: u.width,
217
+ height: u.height
218
+ },
219
+ styles: {
220
+ backgroundColor: "transparent",
221
+ opacity: parseFloat(m.opacity),
222
+ zIndex: 0,
223
+ borderRadius: "0px",
224
+ borderColor: "transparent",
225
+ borderWidth: "0px"
226
+ },
227
+ textContent: y,
228
+ textStyles: O(m),
229
+ dirtyMask: t,
230
+ children: []
231
+ } : null;
232
+ }
233
+ const e = r, i = e.getBoundingClientRect(), s = window.getComputedStyle(e);
234
+ if (i.width === 0 || i.height === 0 || s.display === "none")
159
235
  return null;
160
- let n = i.getAttribute("data-mid");
161
- n || (n = Math.random().toString(36).substring(2, 11), i.setAttribute("data-mid", n));
162
- const r = parseInt(s.zIndex), a = {
236
+ let n = e.getAttribute("data-mid");
237
+ n || (n = Math.random().toString(36).substring(2, 11), e.setAttribute("data-mid", n));
238
+ const o = parseInt(s.zIndex), c = {
163
239
  backgroundColor: s.backgroundColor,
164
240
  opacity: parseFloat(s.opacity),
165
- zIndex: isNaN(r) ? 0 : r,
241
+ zIndex: isNaN(o) ? 0 : o,
166
242
  borderRadius: s.borderRadius,
167
243
  borderColor: s.borderColor,
168
244
  borderWidth: s.borderWidth
169
245
  };
170
- let l = "BOX", m, h;
171
- const p = [];
172
- return z(i) ? (l = "TEXT", m = i.textContent || "", h = L(s)) : Array.from(i.children).forEach((d) => {
173
- const u = S(d, e);
174
- u && p.push(u);
246
+ let d, l;
247
+ const f = [];
248
+ return Array.from(e.childNodes).forEach((h) => {
249
+ const y = E(h, t);
250
+ y && f.push(y);
175
251
  }), {
176
252
  id: n,
177
- type: l,
178
- element: i,
253
+ type: "BOX",
254
+ element: e,
179
255
  rect: {
180
- x: t.left + window.scrollX,
181
- y: t.top + window.scrollY,
182
- width: t.width,
183
- height: t.height
256
+ x: i.left + window.scrollX,
257
+ y: i.top + window.scrollY,
258
+ width: i.width,
259
+ height: i.height
184
260
  },
185
- styles: a,
186
- textContent: m,
187
- textStyles: h,
188
- dirtyMask: e,
189
- children: p
261
+ styles: c,
262
+ textContent: d,
263
+ textStyles: l,
264
+ dirtyMask: t,
265
+ children: f
190
266
  };
191
267
  }
192
- class O {
193
- constructor(e, t) {
194
- o(this, "target");
195
- o(this, "renderer");
196
- o(this, "observer");
197
- o(this, "isDomDirty", !1);
198
- o(this, "isRunning", !1);
199
- o(this, "pendingMask", g);
200
- o(this, "mutationTimer", null);
201
- o(this, "cssTimer", null);
202
- o(this, "onTransitionFinished", (e) => {
203
- this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= T | y, this.cssTimer = window.setTimeout(() => {
268
+ class Y {
269
+ constructor(t, e) {
270
+ a(this, "target");
271
+ a(this, "renderer");
272
+ a(this, "observer");
273
+ a(this, "isDomDirty", !1);
274
+ a(this, "isRunning", !1);
275
+ a(this, "pendingMask", x);
276
+ a(this, "mutationTimer", null);
277
+ a(this, "cssTimer", null);
278
+ a(this, "onTransitionFinished", (t) => {
279
+ this.target.contains(t.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= R | v, this.cssTimer = window.setTimeout(() => {
204
280
  this.isDomDirty = !0, this.cssTimer = null;
205
281
  }, 50));
206
282
  });
207
- o(this, "onWindowResize", () => {
283
+ a(this, "onWindowResize", () => {
208
284
  this.renderer.setSize(window.innerWidth, window.innerHeight), this.isDomDirty = !0;
209
285
  });
210
- o(this, "renderLoop", () => {
286
+ a(this, "renderLoop", () => {
211
287
  this.isRunning && (this.isDomDirty && this.forceUpdateScene(), this.renderer.render(), requestAnimationFrame(this.renderLoop));
212
288
  });
213
- this.target = e, this.renderer = t, this.observer = new MutationObserver((s) => {
214
- let n = g;
215
- for (const r of s)
216
- r.type === "childList" ? n |= w : r.type === "attributes" && (r.attributeName === "style" || r.attributeName === "class") && (n |= T | y);
217
- if (n !== g) {
218
- if (this.pendingMask |= n, n & w) {
289
+ this.target = t, this.renderer = e, this.observer = new MutationObserver((i) => {
290
+ let s = x;
291
+ for (const n of i)
292
+ n.type === "childList" ? s |= C : n.type === "attributes" && (n.attributeName === "style" || n.attributeName === "class") && (s |= R | v);
293
+ if (s !== x) {
294
+ if (this.pendingMask |= s, s & C) {
219
295
  this.clearTimers(), console.log("Structural Change detected"), this.isDomDirty = !0;
220
296
  return;
221
297
  }
@@ -241,27 +317,51 @@ class O {
241
317
  }
242
318
  forceUpdateScene() {
243
319
  this.isDomDirty = !1;
244
- const e = S(this.target, this.pendingMask);
245
- e && this.renderer.syncScene(e), this.pendingMask = g;
320
+ const t = E(this.target, this.pendingMask);
321
+ t && this.renderer.syncScene(t), this.pendingMask = x;
246
322
  }
247
323
  }
248
- class _ {
249
- constructor(e) {
250
- o(this, "renderer");
251
- o(this, "syncer");
252
- o(this, "target");
253
- const t = document.querySelector(e);
254
- if (!t)
255
- throw new Error(`[Mirage] Element not found: ${e}`);
256
- this.target = t, this.renderer = new R(), this.renderer.mount(document.body), this.syncer = new O(this.target, this.renderer);
324
+ class H {
325
+ constructor(t, e) {
326
+ a(this, "renderer");
327
+ a(this, "syncer");
328
+ a(this, "target");
329
+ this.target = t;
330
+ let i;
331
+ if (e.mode === "duplicate" ? i = e.container ?? this.target.parentElement ?? void 0 : i = this.target.parentElement ?? void 0, !i)
332
+ throw new Error("[Mirage] Cannot find a container (parent or option).");
333
+ this.renderer = new _(this.target, e, i), this.renderer.mount(), this.syncer = new Y(this.target, this.renderer);
257
334
  }
258
335
  start() {
259
336
  this.syncer.start();
260
337
  }
261
338
  stop() {
339
+ this.syncer.stop();
340
+ }
341
+ dispose() {
262
342
  this.syncer.stop(), this.renderer.dispose();
263
343
  }
264
344
  }
345
+ class P {
346
+ constructor(t, e = {}) {
347
+ a(this, "_engine");
348
+ if (!t)
349
+ throw new Error("[Mirage] Target element is required.");
350
+ this._engine = new H(t, e);
351
+ }
352
+ start() {
353
+ this._engine.start();
354
+ }
355
+ stop() {
356
+ this._engine.stop();
357
+ }
358
+ /**
359
+ * 엔진 종료 및 메모리 해제
360
+ */
361
+ destroy() {
362
+ this._engine.dispose();
363
+ }
364
+ }
265
365
  export {
266
- _ as Mirage
366
+ P as Mirage
267
367
  };
@@ -1 +1,2 @@
1
- (function(l,d){typeof exports=="object"&&typeof module<"u"?d(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],d):(l=typeof globalThis<"u"?globalThis:l||self,d(l.MirageEngine={},l.THREE))})(this,function(l,d){"use strict";var H=Object.defineProperty;var k=(l,d,T)=>d in l?H(l,d,{enumerable:!0,configurable:!0,writable:!0,value:T}):l[d]=T;var o=(l,d,T)=>(k(l,typeof d!="symbol"?d+"":d,T),T);function T(i){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const t in i)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(i,t);Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>i[t]})}}return e.default=i,Object.freeze(e)}const c=T(d),w=0,S=1,x=2,v=4,E=8,M=16;function D(i,e,t,r){const s=document.createElement("canvas"),n=s.getContext("2d");if(!n)throw new Error("[Mirage] Failed to create canvas context");const a=window.devicePixelRatio||2;s.width=t*a,s.height=r*a,n.scale(a,a),n.font=e.font,n.fillStyle=e.color,n.textAlign=e.textAlign,n.direction=e.direction,n.textBaseline="middle";let m=0;e.textAlign==="center"&&(m=t/2),e.textAlign==="right"&&(m=t);const f=r/2;n.fillText(i,m,f);const h=new c.CanvasTexture(s);return h.colorSpace=c.SRGBColorSpace,h.minFilter=c.LinearFilter,h.needsUpdate=!0,h}class C{constructor(){o(this,"canvas");o(this,"scene");o(this,"camera");o(this,"renderer");o(this,"renderOrder",0);o(this,"meshMap",new Map);this.canvas=document.createElement("canvas"),this.scene=new c.Scene;const e=window.innerWidth,t=window.innerHeight;this.camera=new c.OrthographicCamera(e/-2,e/2,t/2,t/-2,1,1e3),this.camera.position.z=100,this.renderer=new c.WebGLRenderer({canvas:this.canvas,alpha:!0}),this.renderer.setSize(e,t)}mount(e){e.appendChild(this.canvas)}dispose(){try{this.renderer.dispose()}catch{}this.canvas.parentElement&&this.canvas.parentElement.removeChild(this.canvas)}setSize(e,t){this.renderer.setSize(e,t),this.camera.left=e/-2,this.camera.right=e/2,this.camera.top=t/2,this.camera.bottom=t/-2,this.camera.updateProjectionMatrix()}syncScene(e){this.renderOrder=0;const t=new Set;this.reconcileNode(e,t);for(const[r,s]of this.meshMap.entries())t.has(r)||(this.scene.remove(s),s.geometry.dispose(),s.material instanceof c.Material&&s.material.dispose(),this.meshMap.delete(r))}reconcileNode(e,t){var r,s;if(e.type==="BOX"){t.add(e.element);let n=this.meshMap.get(e.element);if(!n){const a=new c.PlaneGeometry(1,1),m=new c.MeshBasicMaterial({transparent:!0});n=new c.Mesh(a,m),this.scene.add(n),this.meshMap.set(e.element,n)}this.updateMeshProperties(n,e);for(const a of e.children)this.reconcileNode(a,t)}if(e.type==="TEXT"){t.add(e.element);let n=this.meshMap.get(e.element);if(!n){const u=new c.PlaneGeometry(1,1),p=new c.MeshBasicMaterial({transparent:!0});n=new c.Mesh(u,p),n.name="BG_MESH",this.scene.add(n),this.meshMap.set(e.element,n)}this.updateMeshProperties(n,e);let a=n.children.find(u=>u.name==="TEXT_CHILD");const m=JSON.stringify(e.textStyles),f=(r=a==null?void 0:a.userData)==null?void 0:r.styleHash,h=e.dirtyMask&M;if(!a||h||m!==f){a&&((s=a.material.map)==null||s.dispose(),a.geometry.dispose(),n.remove(a));const u=D(e.textContent||"",e.textStyles,e.rect.width,e.rect.height),p=new c.PlaneGeometry(1,1),g=new c.MeshBasicMaterial({map:u,transparent:!0,side:c.FrontSide,color:16777215,opacity:1});a=new c.Mesh(p,g),a.name="TEXT_CHILD",a.userData={styleHash:m},a.position.z=.005,n.add(a)}}}updateMeshProperties(e,t){const{rect:r,styles:s}=t,n=this.renderer.domElement.width,a=this.renderer.domElement.height;e.scale.set(r.width,r.height,1);const m=.001;this.renderOrder++,e.position.set(r.x-n/2+r.width/2,-r.y+a/2-r.height/2,s.zIndex+this.renderOrder*m);const f=e.material,h=s.backgroundColor;let y=h,u=1;if(h==="transparent"||h==="rgba(0, 0, 0, 0)")y="#ffffff",u=0;else if(h.startsWith("rgba")){const g=h.match(/[\d.]+/g);if(g&&g.length>=4){const L=g[0],_=g[1],I=g[2];u=parseFloat(g[3]),y=`rgb(${L}, ${_}, ${I})`}}const p=s.opacity*u;f.color.set(y),f.opacity=p,f.transparent=p<1}render(){this.renderer.render(this.scene,this.camera)}}function N(i){var e;return i.nodeType===Node.TEXT_NODE&&(((e=i.textContent)==null?void 0:e.trim().length)||0)>0}function R(i){const e=Array.from(i.childNodes);return e.length===0||e.some(s=>s.nodeType===Node.ELEMENT_NODE)?!1:e.some(N)}function O(i){const e=parseFloat(i.fontSize);let t=parseFloat(i.lineHeight);isNaN(t)&&(t=e*1.2);let r=parseFloat(i.letterSpacing);return isNaN(r)&&(r=0),{font:`${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,color:i.color,textAlign:i.textAlign||"start",textBaseline:"alphabetic",direction:i.direction||"inherit",lineHeight:t,letterSpacing:r}}function b(i,e=S|x|v|M|E){const t=i.getBoundingClientRect(),r=window.getComputedStyle(i);if(t.width===0||t.height===0||r.display==="none")return null;let s=i.getAttribute("data-mid");s||(s=Math.random().toString(36).substring(2,11),i.setAttribute("data-mid",s));const n=parseInt(r.zIndex),a={backgroundColor:r.backgroundColor,opacity:parseFloat(r.opacity),zIndex:isNaN(n)?0:n,borderRadius:r.borderRadius,borderColor:r.borderColor,borderWidth:r.borderWidth};let m="BOX",f,h;const y=[];return R(i)?(m="TEXT",f=i.textContent||"",h=O(r)):Array.from(i.children).forEach(u=>{const p=b(u,e);p&&y.push(p)}),{id:s,type:m,element:i,rect:{x:t.left+window.scrollX,y:t.top+window.scrollY,width:t.width,height:t.height},styles:a,textContent:f,textStyles:h,dirtyMask:e,children:y}}class z{constructor(e,t){o(this,"target");o(this,"renderer");o(this,"observer");o(this,"isDomDirty",!1);o(this,"isRunning",!1);o(this,"pendingMask",w);o(this,"mutationTimer",null);o(this,"cssTimer",null);o(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=S|x,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});o(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});o(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=e,this.renderer=t,this.observer=new MutationObserver(r=>{let s=w;for(const n of r)n.type==="childList"?s|=E:n.type==="attributes"&&(n.attributeName==="style"||n.attributeName==="class")&&(s|=S|x);if(s!==w){if(this.pendingMask|=s,s&E){this.clearTimers(),console.log("Structural Change detected"),this.isDomDirty=!0;return}this.mutationTimer&&clearTimeout(this.mutationTimer),this.mutationTimer=window.setTimeout(()=>{this.mutationTimer=null,this.isDomDirty=!0},200)}})}start(){this.isRunning||(this.isRunning=!0,this.observer.observe(this.target,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),this.target.addEventListener("transitionend",this.onTransitionFinished),this.target.addEventListener("animationend",this.onTransitionFinished),window.addEventListener("resize",this.onWindowResize),this.forceUpdateScene(),this.renderLoop())}stop(){this.isRunning=!1,this.observer.disconnect(),this.clearTimers(),this.target.removeEventListener("transitionend",this.onTransitionFinished),this.target.removeEventListener("animationend",this.onTransitionFinished),window.removeEventListener("resize",this.onWindowResize)}clearTimers(){this.mutationTimer&&(clearTimeout(this.mutationTimer),this.mutationTimer=null),this.cssTimer&&(clearTimeout(this.cssTimer),this.cssTimer=null)}forceUpdateScene(){this.isDomDirty=!1;const e=b(this.target,this.pendingMask);e&&this.renderer.syncScene(e),this.pendingMask=w}}class F{constructor(e){o(this,"renderer");o(this,"syncer");o(this,"target");const t=document.querySelector(e);if(!t)throw new Error(`[Mirage] Element not found: ${e}`);this.target=t,this.renderer=new C,this.renderer.mount(document.body),this.syncer=new z(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop(),this.renderer.dispose()}}l.Mirage=F,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
1
+ (function(m,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],p):(m=typeof globalThis<"u"?globalThis:m||self,p(m.MirageEngine={},m.THREE))})(this,function(m,p){"use strict";var A=Object.defineProperty;var $=(m,p,T)=>p in m?A(m,p,{enumerable:!0,configurable:!0,writable:!0,value:T}):m[p]=T;var a=(m,p,T)=>($(m,typeof p!="symbol"?p+"":p,T),T);function T(r){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const t in r)if(t!=="default"){const i=Object.getOwnPropertyDescriptor(r,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:()=>r[t]})}}return e.default=r,Object.freeze(e)}const d=T(p),v=0,b=1,C=2,z=4,S=8,M=16;function L(r,e,t){const i=e.split(`
2
+ `),s=[];return i.forEach(n=>{const o=n.split(" ");let h=o[0];for(let u=1;u<o.length;u++){const l=o[u];r.measureText(h+" "+l).width<t?h+=" "+l:(s.push(h),h=l)}s.push(h)}),s}function N(r,e,t,i,s=2){const n=document.createElement("canvas"),o=n.getContext("2d");if(!o)throw new Error("[Mirage] Failed to create canvas context");const u=(window.devicePixelRatio||1)*s;n.width=t*u,n.height=i*u,o.scale(u,u),o.font=e.font,o.fillStyle=e.color,o.textBaseline="top",o.globalAlpha=1;const l=L(o,r,t),w=e.lineHeight;l.forEach((x,g)=>{const y=g*w+2;let f=0;e.textAlign==="center"?f=t/2:e.textAlign==="right"&&(f=t),o.textAlign=e.textAlign,o.fillText(x,f,y)});const c=new d.CanvasTexture(n);return c.colorSpace=d.SRGBColorSpace,c.minFilter=d.LinearFilter,c.magFilter=d.LinearFilter,c.needsUpdate=!0,c}class I{constructor(e,t,i){a(this,"canvas");a(this,"scene");a(this,"camera");a(this,"renderer");a(this,"renderOrder",0);a(this,"textQualityFactor",2);a(this,"mode","overlay");a(this,"customZIndex","9999");a(this,"target");a(this,"mountContainer");a(this,"targetRect");a(this,"meshMap",new Map);var o;this.target=e,this.mountContainer=i,this.mode=t.mode??"overlay",(o=t.style)!=null&&o.zIndex&&(this.customZIndex=t.style.zIndex),this.canvas=document.createElement("canvas"),this.scene=new d.Scene,this.targetRect=this.target.getBoundingClientRect();const s=this.targetRect.width,n=this.targetRect.height;this.camera=new d.OrthographicCamera(s/-2,s/2,n/2,n/-2,1,1e3),this.camera.position.z=100,this.renderer=new d.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(s,n),this.applyTextQuality(t.textQuality??"medium")}applyTextQuality(e){if(typeof e=="number"){this.textQualityFactor=Math.max(.1,e);return}switch(e){case"low":this.textQualityFactor=1;break;case"high":this.textQualityFactor=4;break;case"medium":default:this.textQualityFactor=2;break}}mount(){this.mountContainer.appendChild(this.canvas),this.canvas.style.zIndex=this.customZIndex,this.canvas.style.pointerEvents=this.mode==="overlay"?"none":"auto",this.updateCanvasLayout()}updateCanvasLayout(){this.canvas.style.width=`${this.targetRect.width}px`,this.canvas.style.height=`${this.targetRect.height}px`,this.mode==="duplicate"?(this.canvas.style.position="",this.canvas.style.top="",this.canvas.style.left="",this.canvas.style.display="block"):(this.canvas.style.position="absolute",this.canvas.style.top=`${this.target.offsetTop}px`,this.canvas.style.left=`${this.target.offsetLeft}px`,this.canvas.style.display="block")}dispose(){this.renderer.dispose(),this.canvas.remove()}setSize(e,t){this.renderer.setSize(e,t),this.camera.left=e/-2,this.camera.right=e/2,this.camera.top=t/2,this.camera.bottom=t/-2,this.camera.updateProjectionMatrix()}syncScene(e){const t=this.target.getBoundingClientRect(),i=Math.abs(t.width-this.targetRect.width)>.1||Math.abs(t.height-this.targetRect.height)>.1,s=this.mode==="overlay"&&(Math.abs(t.top-this.targetRect.top)>.1||Math.abs(t.left-this.targetRect.left)>.1);i?(this.targetRect=t,this.renderer.setSize(this.targetRect.width,this.targetRect.height),this.camera.left=this.targetRect.width/-2,this.camera.right=this.targetRect.width/2,this.camera.top=this.targetRect.height/2,this.camera.bottom=this.targetRect.height/-2,this.camera.updateProjectionMatrix(),this.updateCanvasLayout()):s?(this.targetRect=t,this.updateCanvasLayout()):this.targetRect=t,this.renderOrder=0;const n=new Set;this.reconcileNode(e,n);for(const[o,h]of this.meshMap.entries())n.has(o)||(this.scene.remove(h),h.geometry.dispose(),h.material instanceof d.Material&&h.material.dispose(),this.meshMap.delete(o))}reconcileNode(e,t){t.add(e.element);let i=this.meshMap.get(e.element);if(!i){const s=new d.PlaneGeometry(1,1),n=new d.MeshBasicMaterial({transparent:!0});i=new d.Mesh(s,n),e.type==="TEXT"&&(i.name="BG_MESH"),this.scene.add(i),this.meshMap.set(e.element,i)}if(i.userData.domRect=e.rect,this.updateMeshProperties(i,e),e.type==="BOX")for(const s of e.children)this.reconcileNode(s,t);else e.type==="TEXT"&&this.reconcileTextChild(i,e)}reconcileTextChild(e,t){var h,u;let i=e.children.find(l=>l.name==="TEXT_CHILD");const s=JSON.stringify(t.textStyles),n=(h=i==null?void 0:i.userData)==null?void 0:h.styleHash;if(!i||t.dirtyMask&M||s!==n){i&&((u=i.material.map)==null||u.dispose(),i.geometry.dispose(),e.remove(i));const l=N(t.textContent||"",t.textStyles,t.rect.width,t.rect.height,this.textQualityFactor),w=new d.PlaneGeometry(1,1),c=new d.MeshBasicMaterial({map:l,transparent:!0,side:d.FrontSide,color:16777215});i=new d.Mesh(w,c),i.name="TEXT_CHILD",i.userData={styleHash:s},e.add(i)}if(i){const l=e.userData.domRect,w=l.x+l.width/2,c=l.y+l.height/2,x=t.rect.x+t.rect.width/2,g=t.rect.y+t.rect.height/2,y=x-w,f=-(g-c);i.position.set(y,f,.005)}}updateMeshProperties(e,t){const{rect:i,styles:s}=t,n=this.renderer.getPixelRatio(),o=this.renderer.domElement.width/n,h=this.renderer.domElement.height/n;e.scale.set(i.width,i.height,1);const u=.001;this.renderOrder++;const l=this.targetRect.left+window.scrollX,w=this.targetRect.top+window.scrollY,c=i.x-l,x=i.y-w;e.position.set(c-o/2+i.width/2,-x+h/2-i.height/2,s.zIndex+this.renderOrder*u);const g=e.material,y=s.backgroundColor;let f=y,E=1;if(y==="transparent"||y==="rgba(0, 0, 0, 0)")f="#ffffff",E=0;else if(y.startsWith("rgba")){const R=y.match(/[\d.]+/g);if(R&&R.length>=4){const P=R[0],Y=R[1],B=R[2];E=parseFloat(R[3]),f=`rgb(${P}, ${Y}, ${B})`}}const F=s.opacity*E;g.color.set(f),g.opacity=F,g.transparent=F<1}render(){this.renderer.render(this.scene,this.camera)}}function O(r){const e=document.createRange();e.selectNodeContents(r);const t=e.getBoundingClientRect();return{left:t.left,top:t.top,width:t.width,height:t.height}}function _(r){const e=parseFloat(r.fontSize);let t=parseFloat(r.lineHeight);isNaN(t)&&(t=e*1.2);let i=parseFloat(r.letterSpacing);return isNaN(i)&&(i=0),{font:`${r.fontStyle} ${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,color:r.color,textAlign:r.textAlign||"start",textBaseline:"alphabetic",direction:r.direction||"inherit",lineHeight:t,letterSpacing:i}}function D(r,e=b|C|z|M|S){if(r.nodeType===Node.TEXT_NODE){const c=r;if(!c.textContent||!c.textContent.trim())return null;const x=c.textContent.replace(/\s+/g," ").trim();if(x.length===0)return null;const g=O(c);if(g.width===0||g.height===0)return null;const y=c.parentElement,f=y?window.getComputedStyle(y):null;return f?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:c,rect:{x:g.left+window.scrollX,y:g.top+window.scrollY,width:g.width,height:g.height},styles:{backgroundColor:"transparent",opacity:parseFloat(f.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:x,textStyles:_(f),dirtyMask:e,children:[]}:null}const t=r,i=t.getBoundingClientRect(),s=window.getComputedStyle(t);if(i.width===0||i.height===0||s.display==="none")return null;let n=t.getAttribute("data-mid");n||(n=Math.random().toString(36).substring(2,11),t.setAttribute("data-mid",n));const o=parseInt(s.zIndex),h={backgroundColor:s.backgroundColor,opacity:parseFloat(s.opacity),zIndex:isNaN(o)?0:o,borderRadius:s.borderRadius,borderColor:s.borderColor,borderWidth:s.borderWidth};let u,l;const w=[];return Array.from(t.childNodes).forEach(c=>{const x=D(c,e);x&&w.push(x)}),{id:n,type:"BOX",element:t,rect:{x:i.left+window.scrollX,y:i.top+window.scrollY,width:i.width,height:i.height},styles:h,textContent:u,textStyles:l,dirtyMask:e,children:w}}class k{constructor(e,t){a(this,"target");a(this,"renderer");a(this,"observer");a(this,"isDomDirty",!1);a(this,"isRunning",!1);a(this,"pendingMask",v);a(this,"mutationTimer",null);a(this,"cssTimer",null);a(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=b|C,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});a(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});a(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=e,this.renderer=t,this.observer=new MutationObserver(i=>{let s=v;for(const n of i)n.type==="childList"?s|=S:n.type==="attributes"&&(n.attributeName==="style"||n.attributeName==="class")&&(s|=b|C);if(s!==v){if(this.pendingMask|=s,s&S){this.clearTimers(),console.log("Structural Change detected"),this.isDomDirty=!0;return}this.mutationTimer&&clearTimeout(this.mutationTimer),this.mutationTimer=window.setTimeout(()=>{this.mutationTimer=null,this.isDomDirty=!0},200)}})}start(){this.isRunning||(this.isRunning=!0,this.observer.observe(this.target,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),this.target.addEventListener("transitionend",this.onTransitionFinished),this.target.addEventListener("animationend",this.onTransitionFinished),window.addEventListener("resize",this.onWindowResize),this.forceUpdateScene(),this.renderLoop())}stop(){this.isRunning=!1,this.observer.disconnect(),this.clearTimers(),this.target.removeEventListener("transitionend",this.onTransitionFinished),this.target.removeEventListener("animationend",this.onTransitionFinished),window.removeEventListener("resize",this.onWindowResize)}clearTimers(){this.mutationTimer&&(clearTimeout(this.mutationTimer),this.mutationTimer=null),this.cssTimer&&(clearTimeout(this.cssTimer),this.cssTimer=null)}forceUpdateScene(){this.isDomDirty=!1;const e=D(this.target,this.pendingMask);e&&this.renderer.syncScene(e),this.pendingMask=v}}class X{constructor(e,t){a(this,"renderer");a(this,"syncer");a(this,"target");this.target=e;let i;if(t.mode==="duplicate"?i=t.container??this.target.parentElement??void 0:i=this.target.parentElement??void 0,!i)throw new Error("[Mirage] Cannot find a container (parent or option).");this.renderer=new I(this.target,t,i),this.renderer.mount(),this.syncer=new k(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}class H{constructor(e,t={}){a(this,"_engine");if(!e)throw new Error("[Mirage] Target element is required.");this._engine=new X(e,t)}start(){this._engine.start()}stop(){this._engine.stop()}destroy(){this._engine.dispose()}}m.Mirage=H,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})});
@@ -0,0 +1,10 @@
1
+ import { MirageConfig } from '../types';
2
+ export declare class Engine {
3
+ private renderer;
4
+ private syncer;
5
+ private target;
6
+ constructor(target: HTMLElement, config: MirageConfig);
7
+ start(): void;
8
+ stop(): void;
9
+ dispose(): void;
10
+ }
@@ -1,8 +1,11 @@
1
+ import { MirageConfig } from '../types';
1
2
  export declare class Mirage {
2
- private renderer;
3
- private syncer;
4
- private target;
5
- constructor(selector: string);
3
+ private _engine;
4
+ constructor(element: HTMLElement, config?: MirageConfig);
6
5
  start(): void;
7
6
  stop(): void;
7
+ /**
8
+ * 엔진 종료 및 메모리 해제
9
+ */
10
+ destroy(): void;
8
11
  }
@@ -1,2 +1,2 @@
1
1
  import { SceneNode } from '../types';
2
- export declare function extractSceneGraph(element: HTMLElement, initialMask?: number): SceneNode | null;
2
+ export declare function extractSceneGraph(sourceNode: HTMLElement | Node, initialMask?: number): SceneNode | null;
@@ -1,17 +1,26 @@
1
- import { SceneNode } from '../types';
1
+ import { SceneNode, MirageConfig } from '../types';
2
2
  export declare class Renderer {
3
3
  readonly canvas: HTMLCanvasElement;
4
4
  private readonly scene;
5
5
  private readonly camera;
6
6
  private readonly renderer;
7
7
  private renderOrder;
8
+ private textQualityFactor;
9
+ private mode;
10
+ private customZIndex;
11
+ private target;
12
+ private mountContainer;
13
+ private targetRect;
8
14
  private meshMap;
9
- constructor();
10
- mount(parent: HTMLElement): void;
15
+ constructor(target: HTMLElement, config: MirageConfig, mountContainer: HTMLElement);
16
+ private applyTextQuality;
17
+ mount(): void;
18
+ private updateCanvasLayout;
11
19
  dispose(): void;
12
20
  setSize(width: number, height: number): void;
13
21
  syncScene(graphNode: SceneNode): void;
14
22
  private reconcileNode;
23
+ private reconcileTextChild;
15
24
  private updateMeshProperties;
16
25
  render(): void;
17
26
  }
@@ -0,0 +1,3 @@
1
+ import { TextStyles } from '../../types';
2
+ import * as THREE from "three";
3
+ export declare function createTextTexture(text: string, styles: TextStyles, rectWidth: number, rectHeight: number, qualityFactor?: number): THREE.CanvasTexture;
@@ -0,0 +1,35 @@
1
+ export type NodeType = "BOX" | "TEXT";
2
+ export interface NodeRect {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface BoxStyles {
9
+ backgroundColor: string;
10
+ opacity: number;
11
+ zIndex: number;
12
+ borderRadius: string;
13
+ borderColor: string;
14
+ borderWidth: string;
15
+ }
16
+ export interface TextStyles {
17
+ font: string;
18
+ color: string;
19
+ textAlign: CanvasTextAlign;
20
+ textBaseline: CanvasTextBaseline;
21
+ direction: CanvasDirection;
22
+ lineHeight: number;
23
+ letterSpacing: number;
24
+ }
25
+ export interface SceneNode {
26
+ id: string;
27
+ type: NodeType;
28
+ element: HTMLElement;
29
+ rect: NodeRect;
30
+ styles: BoxStyles;
31
+ textContent?: string;
32
+ textStyles?: TextStyles;
33
+ dirtyMask: number;
34
+ children: SceneNode[];
35
+ }
@@ -0,0 +1,18 @@
1
+ export type TextQuality = "low" | "medium" | "high" | number;
2
+ export type MirageMode = "overlay" | "duplicate";
3
+ interface BaseConfig {
4
+ debug?: boolean;
5
+ textQuality?: TextQuality;
6
+ style?: {
7
+ zIndex?: string;
8
+ };
9
+ }
10
+ export interface OverlayConfig extends BaseConfig {
11
+ mode?: "overlay";
12
+ }
13
+ export interface DuplicateConfig extends BaseConfig {
14
+ mode: "duplicate";
15
+ container?: HTMLElement;
16
+ }
17
+ export type MirageConfig = OverlayConfig | DuplicateConfig;
18
+ export {};
@@ -0,0 +1,6 @@
1
+ export declare const DIRTY_NONE = 0;
2
+ export declare const DIRTY_RECT: number;
3
+ export declare const DIRTY_STYLE: number;
4
+ export declare const DIRTY_ZINDEX: number;
5
+ export declare const DIRTY_STRUCTURE: number;
6
+ export declare const DIRTY_CONTENT: number;
@@ -1,41 +1,3 @@
1
- export type NodeType = "BOX" | "TEXT";
2
- export interface NodeRect {
3
- x: number;
4
- y: number;
5
- width: number;
6
- height: number;
7
- }
8
- export interface BoxStyles {
9
- backgroundColor: string;
10
- opacity: number;
11
- zIndex: number;
12
- borderRadius: string;
13
- borderColor: string;
14
- borderWidth: string;
15
- }
16
- export interface TextStyles {
17
- font: string;
18
- color: string;
19
- textAlign: CanvasTextAlign;
20
- textBaseline: CanvasTextBaseline;
21
- direction: CanvasDirection;
22
- lineHeight: number;
23
- letterSpacing: number;
24
- }
25
- export interface SceneNode {
26
- id: string;
27
- type: NodeType;
28
- element: HTMLElement;
29
- rect: NodeRect;
30
- styles: BoxStyles;
31
- textContent?: string;
32
- textStyles?: TextStyles;
33
- dirtyMask: number;
34
- children: SceneNode[];
35
- }
36
- export declare const DIRTY_NONE = 0;
37
- export declare const DIRTY_RECT: number;
38
- export declare const DIRTY_STYLE: number;
39
- export declare const DIRTY_ZINDEX: number;
40
- export declare const DIRTY_STRUCTURE: number;
41
- export declare const DIRTY_CONTENT: number;
1
+ export * from './config';
2
+ export * from './common';
3
+ export * from './flags';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Converts DOM coordinates (Top-Left 0,0) to WebGL coordinates (Center 0,0).
3
+ * @param x - The x coordinate of the DOM element (Left)
4
+ * @param y - The y coordinate of the DOM element (Top)
5
+ * @param width - The width of the DOM element
6
+ * @param height - The height of the DOM element
7
+ * @param canvasWidth - The total width of the canvas
8
+ * @param canvasHeight - The total height of the canvas
9
+ */
10
+ export declare function domToWebGL(x: number, y: number, width: number, height: number, canvasWidth: number, canvasHeight: number): {
11
+ x: number;
12
+ y: number;
13
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirage-engine",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "An engine that mirrors HTML DOM elements to a WebGL scene in real-time.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -1,4 +0,0 @@
1
- export declare function domToWebGL(x: number, y: number, canvasWidth: number, canvasHeight: number): {
2
- x: number;
3
- y: number;
4
- };
@@ -1,3 +0,0 @@
1
- import { TextStyles } from '../types';
2
- import * as THREE from "three";
3
- export declare function createTextTexture(text: string, styles: TextStyles, rectWidth: number, rectHeight: number): THREE.CanvasTexture;