mirage-engine 0.2.5 → 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,268 +1,297 @@
1
- var v = Object.defineProperty;
2
- var D = (n, e, t) => e in n ? v(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t;
3
- var o = (n, e, t) => (D(n, typeof e != "symbol" ? e + "" : e, t), t);
4
- import * as m from "three";
5
- const T = 0, y = 1, x = 2, M = 4, S = 8, C = 16;
6
- function F(n, e, t) {
7
- const i = e.split(`
8
- `), r = [];
9
- return i.forEach((s) => {
10
- const g = s.split(" ");
11
- let l = g[0];
12
- for (let p = 1; p < g.length; p++) {
13
- const a = g[p];
14
- n.measureText(l + " " + a).width < t ? l += " " + a : (r.push(l), l = a);
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
15
  }
16
- r.push(l);
17
- }), r;
16
+ s.push(c);
17
+ }), s;
18
18
  }
19
- function N(n, e, t, i) {
20
- const r = document.createElement("canvas"), s = r.getContext("2d");
21
- if (!s)
19
+ function X(r, t, e, i, s = 2) {
20
+ const n = document.createElement("canvas"), o = n.getContext("2d");
21
+ if (!o)
22
22
  throw new Error("[Mirage] Failed to create canvas context");
23
- const g = 2, l = (window.devicePixelRatio || 2) * g;
24
- r.width = t * l, r.height = i * l, s.scale(l, l), s.font = e.font, s.fillStyle = e.color, s.textBaseline = "top", s.globalAlpha = 1;
25
- const p = F(s, n, t), a = e.lineHeight;
26
- p.forEach((h, u) => {
27
- const f = u * a + 2;
28
- let d = 0;
29
- e.textAlign === "center" ? d = t / 2 : e.textAlign === "right" && (d = t), s.textAlign = e.textAlign, s.fillText(h, d, f);
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
30
  });
31
- const c = new m.CanvasTexture(r);
32
- return c.colorSpace = m.SRGBColorSpace, c.minFilter = m.LinearFilter, c.magFilter = m.LinearFilter, c.needsUpdate = !0, c;
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;
33
33
  }
34
- class L {
35
- constructor() {
36
- o(this, "canvas");
37
- o(this, "scene");
38
- o(this, "camera");
39
- o(this, "renderer");
40
- o(this, "renderOrder", 0);
41
- o(this, "meshMap", /* @__PURE__ */ new Map());
42
- this.canvas = document.createElement("canvas"), this.scene = new m.Scene();
43
- const e = window.innerWidth, t = window.innerHeight;
44
- this.camera = new m.OrthographicCamera(
45
- e / -2,
46
- e / 2,
47
- t / 2,
48
- 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,
49
56
  1,
50
57
  1e3
51
- ), this.camera.position.z = 100, this.renderer = new m.WebGLRenderer({
58
+ ), this.camera.position.z = 100, this.renderer = new g.WebGLRenderer({
52
59
  canvas: this.canvas,
53
- alpha: !0
54
- }), this.renderer.setPixelRatio(window.devicePixelRatio), 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");
55
63
  }
56
- mount(e) {
57
- e.appendChild(this.canvas);
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
+ }
81
+ }
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");
58
87
  }
59
88
  dispose() {
60
- try {
61
- this.renderer.dispose();
62
- } catch {
63
- }
64
- this.canvas.parentElement && this.canvas.parentElement.removeChild(this.canvas);
89
+ this.renderer.dispose(), this.canvas.remove();
65
90
  }
66
- setSize(e, t) {
67
- 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();
68
93
  }
69
- syncScene(e) {
70
- this.renderOrder = 0;
71
- const t = /* @__PURE__ */ new Set();
72
- this.reconcileNode(e, t);
73
- for (const [i, r] of this.meshMap.entries())
74
- t.has(i) || (this.scene.remove(r), r.geometry.dispose(), r.material instanceof m.Material && r.material.dispose(), this.meshMap.delete(i));
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));
75
101
  }
76
- reconcileNode(e, t) {
77
- t.add(e.element);
78
- let i = this.meshMap.get(e.element);
102
+ reconcileNode(t, e) {
103
+ e.add(t.element);
104
+ let i = this.meshMap.get(t.element);
79
105
  if (!i) {
80
- const r = new m.PlaneGeometry(1, 1), s = new m.MeshBasicMaterial({ transparent: !0 });
81
- i = new m.Mesh(r, s), e.type === "TEXT" && (i.name = "BG_MESH"), this.scene.add(i), this.meshMap.set(e.element, 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);
82
108
  }
83
- if (i.userData.domRect = e.rect, this.updateMeshProperties(i, e), e.type === "BOX")
84
- for (const r of e.children)
85
- this.reconcileNode(r, t);
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);
86
112
  else
87
- e.type === "TEXT" && this.reconcileTextChild(i, e);
113
+ t.type === "TEXT" && this.reconcileTextChild(i, t);
88
114
  }
89
- reconcileTextChild(e, t) {
90
- var l, p;
91
- let i = e.children.find(
92
- (a) => a.name === "TEXT_CHILD"
115
+ reconcileTextChild(t, e) {
116
+ var c, d;
117
+ let i = t.children.find(
118
+ (l) => l.name === "TEXT_CHILD"
93
119
  );
94
- const r = JSON.stringify(t.textStyles), s = (l = i == null ? void 0 : i.userData) == null ? void 0 : l.styleHash;
95
- if (!i || t.dirtyMask & C || r !== s) {
96
- i && ((p = i.material.map) == null || p.dispose(), i.geometry.dispose(), e.remove(i));
97
- const a = N(
98
- t.textContent || "",
99
- t.textStyles,
100
- t.rect.width,
101
- t.rect.height
102
- ), c = new m.PlaneGeometry(1, 1), h = new m.MeshBasicMaterial({
103
- map: a,
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,
104
131
  transparent: !0,
105
- side: m.FrontSide,
132
+ side: g.FrontSide,
106
133
  color: 16777215
107
134
  });
108
- i = new m.Mesh(c, h), i.name = "TEXT_CHILD", i.userData = { styleHash: r }, e.add(i);
135
+ i = new g.Mesh(f, h), i.name = "TEXT_CHILD", i.userData = { styleHash: s }, t.add(i);
109
136
  }
110
137
  if (i) {
111
- const a = e.userData.domRect, c = a.x + a.width / 2, h = a.y + a.height / 2, u = t.rect.x + t.rect.width / 2, f = t.rect.y + t.rect.height / 2, d = u - c, w = -(f - h);
112
- i.position.set(d, w, 5e-3);
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);
113
140
  }
114
141
  }
115
- updateMeshProperties(e, t) {
116
- const { rect: i, styles: r } = t, s = this.renderer.getPixelRatio(), g = this.renderer.domElement.width / s, l = this.renderer.domElement.height / s;
117
- e.scale.set(i.width, i.height, 1);
118
- const p = 1e-3;
119
- this.renderOrder++, e.position.set(
120
- i.x - g / 2 + i.width / 2,
121
- -i.y + l / 2 - i.height / 2,
122
- r.zIndex + this.renderOrder * p
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
123
152
  );
124
- const a = e.material, c = r.backgroundColor;
125
- let h = c, u = 1;
126
- if (c === "transparent" || c === "rgba(0, 0, 0, 0)")
127
- h = "#ffffff", u = 0;
128
- else if (c.startsWith("rgba")) {
129
- const d = c.match(/[\d.]+/g);
130
- if (d && d.length >= 4) {
131
- const w = d[0], R = d[1], E = d[2];
132
- u = parseFloat(d[3]), h = `rgb(${w}, ${R}, ${E})`;
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})`;
133
162
  }
134
163
  }
135
- const f = r.opacity * u;
136
- a.color.set(h), a.opacity = f, a.transparent = f < 1;
164
+ const b = s.opacity * T;
165
+ u.color.set(m), u.opacity = b, u.transparent = b < 1;
137
166
  }
138
167
  render() {
139
168
  this.renderer.render(this.scene, this.camera);
140
169
  }
141
170
  }
142
- function z(n) {
143
- const e = document.createRange();
144
- e.selectNodeContents(n);
145
- const t = e.getBoundingClientRect();
171
+ function k(r) {
172
+ const t = document.createRange();
173
+ t.selectNodeContents(r);
174
+ const e = t.getBoundingClientRect();
146
175
  return {
147
- left: t.left,
148
- top: t.top,
149
- width: t.width,
150
- height: t.height
176
+ left: e.left,
177
+ top: e.top,
178
+ width: e.width,
179
+ height: e.height
151
180
  };
152
181
  }
153
- function I(n) {
154
- const e = parseFloat(n.fontSize);
155
- let t = parseFloat(n.lineHeight);
156
- isNaN(t) && (t = e * 1.2);
157
- let i = parseFloat(n.letterSpacing);
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);
158
187
  return isNaN(i) && (i = 0), {
159
- font: `${n.fontStyle} ${n.fontWeight} ${n.fontSize} ${n.fontFamily}`,
160
- color: n.color,
161
- textAlign: n.textAlign || "start",
188
+ font: `${r.fontStyle} ${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,
189
+ color: r.color,
190
+ textAlign: r.textAlign || "start",
162
191
  textBaseline: "alphabetic",
163
- direction: n.direction || "inherit",
164
- lineHeight: t,
192
+ direction: r.direction || "inherit",
193
+ lineHeight: e,
165
194
  letterSpacing: i
166
195
  };
167
196
  }
168
- function b(n, e = y | x | M | C | S) {
169
- if (n.nodeType === Node.TEXT_NODE) {
170
- const h = n;
197
+ function E(r, t = R | v | I | S | C) {
198
+ if (r.nodeType === Node.TEXT_NODE) {
199
+ const h = r;
171
200
  if (!h.textContent || !h.textContent.trim())
172
201
  return null;
173
- const u = h.textContent.replace(/\s+/g, " ").trim();
174
- if (u.length === 0)
202
+ const y = h.textContent.replace(/\s+/g, " ").trim();
203
+ if (y.length === 0)
175
204
  return null;
176
- const f = z(h);
177
- if (f.width === 0 || f.height === 0)
205
+ const u = k(h);
206
+ if (u.width === 0 || u.height === 0)
178
207
  return null;
179
- const d = h.parentElement, w = d ? window.getComputedStyle(d) : null;
180
- return w ? {
208
+ const p = h.parentElement, m = p ? window.getComputedStyle(p) : null;
209
+ return m ? {
181
210
  id: Math.random().toString(36).substring(2, 9),
182
211
  type: "TEXT",
183
212
  element: h,
184
213
  rect: {
185
- x: f.left + window.scrollX,
186
- y: f.top + window.scrollY,
187
- width: f.width,
188
- height: f.height
214
+ x: u.left + window.scrollX,
215
+ y: u.top + window.scrollY,
216
+ width: u.width,
217
+ height: u.height
189
218
  },
190
219
  styles: {
191
220
  backgroundColor: "transparent",
192
- opacity: parseFloat(w.opacity),
221
+ opacity: parseFloat(m.opacity),
193
222
  zIndex: 0,
194
223
  borderRadius: "0px",
195
224
  borderColor: "transparent",
196
225
  borderWidth: "0px"
197
226
  },
198
- textContent: u,
199
- textStyles: I(w),
200
- dirtyMask: e,
227
+ textContent: y,
228
+ textStyles: O(m),
229
+ dirtyMask: t,
201
230
  children: []
202
231
  } : null;
203
232
  }
204
- const t = n, i = t.getBoundingClientRect(), r = window.getComputedStyle(t);
205
- if (i.width === 0 || i.height === 0 || r.display === "none")
233
+ const e = r, i = e.getBoundingClientRect(), s = window.getComputedStyle(e);
234
+ if (i.width === 0 || i.height === 0 || s.display === "none")
206
235
  return null;
207
- let s = t.getAttribute("data-mid");
208
- s || (s = Math.random().toString(36).substring(2, 11), t.setAttribute("data-mid", s));
209
- const g = parseInt(r.zIndex), l = {
210
- backgroundColor: r.backgroundColor,
211
- opacity: parseFloat(r.opacity),
212
- zIndex: isNaN(g) ? 0 : g,
213
- borderRadius: r.borderRadius,
214
- borderColor: r.borderColor,
215
- borderWidth: r.borderWidth
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 = {
239
+ backgroundColor: s.backgroundColor,
240
+ opacity: parseFloat(s.opacity),
241
+ zIndex: isNaN(o) ? 0 : o,
242
+ borderRadius: s.borderRadius,
243
+ borderColor: s.borderColor,
244
+ borderWidth: s.borderWidth
216
245
  };
217
- let p, a;
218
- const c = [];
219
- return Array.from(t.childNodes).forEach((h) => {
220
- const u = b(h, e);
221
- u && c.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);
222
251
  }), {
223
- id: s,
252
+ id: n,
224
253
  type: "BOX",
225
- element: t,
254
+ element: e,
226
255
  rect: {
227
256
  x: i.left + window.scrollX,
228
257
  y: i.top + window.scrollY,
229
258
  width: i.width,
230
259
  height: i.height
231
260
  },
232
- styles: l,
233
- textContent: p,
234
- textStyles: a,
235
- dirtyMask: e,
236
- children: c
261
+ styles: c,
262
+ textContent: d,
263
+ textStyles: l,
264
+ dirtyMask: t,
265
+ children: f
237
266
  };
238
267
  }
239
- class H {
240
- constructor(e, t) {
241
- o(this, "target");
242
- o(this, "renderer");
243
- o(this, "observer");
244
- o(this, "isDomDirty", !1);
245
- o(this, "isRunning", !1);
246
- o(this, "pendingMask", T);
247
- o(this, "mutationTimer", null);
248
- o(this, "cssTimer", null);
249
- o(this, "onTransitionFinished", (e) => {
250
- this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= y | x, 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(() => {
251
280
  this.isDomDirty = !0, this.cssTimer = null;
252
281
  }, 50));
253
282
  });
254
- o(this, "onWindowResize", () => {
283
+ a(this, "onWindowResize", () => {
255
284
  this.renderer.setSize(window.innerWidth, window.innerHeight), this.isDomDirty = !0;
256
285
  });
257
- o(this, "renderLoop", () => {
286
+ a(this, "renderLoop", () => {
258
287
  this.isRunning && (this.isDomDirty && this.forceUpdateScene(), this.renderer.render(), requestAnimationFrame(this.renderLoop));
259
288
  });
260
- this.target = e, this.renderer = t, this.observer = new MutationObserver((i) => {
261
- let r = T;
262
- for (const s of i)
263
- s.type === "childList" ? r |= S : s.type === "attributes" && (s.attributeName === "style" || s.attributeName === "class") && (r |= y | x);
264
- if (r !== T) {
265
- if (this.pendingMask |= r, r & S) {
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) {
266
295
  this.clearTimers(), console.log("Structural Change detected"), this.isDomDirty = !0;
267
296
  return;
268
297
  }
@@ -288,27 +317,51 @@ class H {
288
317
  }
289
318
  forceUpdateScene() {
290
319
  this.isDomDirty = !1;
291
- const e = b(this.target, this.pendingMask);
292
- e && this.renderer.syncScene(e), this.pendingMask = T;
320
+ const t = E(this.target, this.pendingMask);
321
+ t && this.renderer.syncScene(t), this.pendingMask = x;
293
322
  }
294
323
  }
295
- class X {
296
- constructor(e) {
297
- o(this, "renderer");
298
- o(this, "syncer");
299
- o(this, "target");
300
- const t = document.querySelector(e);
301
- if (!t)
302
- throw new Error(`[Mirage] Element not found: ${e}`);
303
- this.target = t, this.renderer = new L(), this.renderer.mount(document.body), this.syncer = new H(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);
304
334
  }
305
335
  start() {
306
336
  this.syncer.start();
307
337
  }
308
338
  stop() {
339
+ this.syncer.stop();
340
+ }
341
+ dispose() {
309
342
  this.syncer.stop(), this.renderer.dispose();
310
343
  }
311
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
+ }
312
365
  export {
313
- X as Mirage
366
+ P as Mirage
314
367
  };
@@ -1,2 +1,2 @@
1
- (function(m,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],f):(m=typeof globalThis<"u"?globalThis:m||self,f(m.MirageEngine={},m.THREE))})(this,function(m,f){"use strict";var X=Object.defineProperty;var k=(m,f,y)=>f in m?X(m,f,{enumerable:!0,configurable:!0,writable:!0,value:y}):m[f]=y;var o=(m,f,y)=>(k(m,typeof f!="symbol"?f+"":f,y),y);function y(n){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(n){for(const t in n)if(t!=="default"){const i=Object.getOwnPropertyDescriptor(n,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:()=>n[t]})}}return e.default=n,Object.freeze(e)}const d=y(f),S=0,b=1,C=2,D=4,E=8,R=16;function M(n,e,t){const i=e.split(`
2
- `),r=[];return i.forEach(s=>{const T=s.split(" ");let l=T[0];for(let w=1;w<T.length;w++){const a=T[w];n.measureText(l+" "+a).width<t?l+=" "+a:(r.push(l),l=a)}r.push(l)}),r}function F(n,e,t,i){const r=document.createElement("canvas"),s=r.getContext("2d");if(!s)throw new Error("[Mirage] Failed to create canvas context");const T=2,l=(window.devicePixelRatio||2)*T;r.width=t*l,r.height=i*l,s.scale(l,l),s.font=e.font,s.fillStyle=e.color,s.textBaseline="top",s.globalAlpha=1;const w=M(s,n,t),a=e.lineHeight;w.forEach((h,p)=>{const g=p*a+2;let u=0;e.textAlign==="center"?u=t/2:e.textAlign==="right"&&(u=t),s.textAlign=e.textAlign,s.fillText(h,u,g)});const c=new d.CanvasTexture(r);return c.colorSpace=d.SRGBColorSpace,c.minFilter=d.LinearFilter,c.magFilter=d.LinearFilter,c.needsUpdate=!0,c}class N{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 d.Scene;const e=window.innerWidth,t=window.innerHeight;this.camera=new d.OrthographicCamera(e/-2,e/2,t/2,t/-2,1,1e3),this.camera.position.z=100,this.renderer=new d.WebGLRenderer({canvas:this.canvas,alpha:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),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[i,r]of this.meshMap.entries())t.has(i)||(this.scene.remove(r),r.geometry.dispose(),r.material instanceof d.Material&&r.material.dispose(),this.meshMap.delete(i))}reconcileNode(e,t){t.add(e.element);let i=this.meshMap.get(e.element);if(!i){const r=new d.PlaneGeometry(1,1),s=new d.MeshBasicMaterial({transparent:!0});i=new d.Mesh(r,s),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 r of e.children)this.reconcileNode(r,t);else e.type==="TEXT"&&this.reconcileTextChild(i,e)}reconcileTextChild(e,t){var l,w;let i=e.children.find(a=>a.name==="TEXT_CHILD");const r=JSON.stringify(t.textStyles),s=(l=i==null?void 0:i.userData)==null?void 0:l.styleHash;if(!i||t.dirtyMask&R||r!==s){i&&((w=i.material.map)==null||w.dispose(),i.geometry.dispose(),e.remove(i));const a=F(t.textContent||"",t.textStyles,t.rect.width,t.rect.height),c=new d.PlaneGeometry(1,1),h=new d.MeshBasicMaterial({map:a,transparent:!0,side:d.FrontSide,color:16777215});i=new d.Mesh(c,h),i.name="TEXT_CHILD",i.userData={styleHash:r},e.add(i)}if(i){const a=e.userData.domRect,c=a.x+a.width/2,h=a.y+a.height/2,p=t.rect.x+t.rect.width/2,g=t.rect.y+t.rect.height/2,u=p-c,x=-(g-h);i.position.set(u,x,.005)}}updateMeshProperties(e,t){const{rect:i,styles:r}=t,s=this.renderer.getPixelRatio(),T=this.renderer.domElement.width/s,l=this.renderer.domElement.height/s;e.scale.set(i.width,i.height,1);const w=.001;this.renderOrder++,e.position.set(i.x-T/2+i.width/2,-i.y+l/2-i.height/2,r.zIndex+this.renderOrder*w);const a=e.material,c=r.backgroundColor;let h=c,p=1;if(c==="transparent"||c==="rgba(0, 0, 0, 0)")h="#ffffff",p=0;else if(c.startsWith("rgba")){const u=c.match(/[\d.]+/g);if(u&&u.length>=4){const x=u[0],I=u[1],_=u[2];p=parseFloat(u[3]),h=`rgb(${x}, ${I}, ${_})`}}const g=r.opacity*p;a.color.set(h),a.opacity=g,a.transparent=g<1}render(){this.renderer.render(this.scene,this.camera)}}function O(n){const e=document.createRange();e.selectNodeContents(n);const t=e.getBoundingClientRect();return{left:t.left,top:t.top,width:t.width,height:t.height}}function z(n){const e=parseFloat(n.fontSize);let t=parseFloat(n.lineHeight);isNaN(t)&&(t=e*1.2);let i=parseFloat(n.letterSpacing);return isNaN(i)&&(i=0),{font:`${n.fontStyle} ${n.fontWeight} ${n.fontSize} ${n.fontFamily}`,color:n.color,textAlign:n.textAlign||"start",textBaseline:"alphabetic",direction:n.direction||"inherit",lineHeight:t,letterSpacing:i}}function v(n,e=b|C|D|R|E){if(n.nodeType===Node.TEXT_NODE){const h=n;if(!h.textContent||!h.textContent.trim())return null;const p=h.textContent.replace(/\s+/g," ").trim();if(p.length===0)return null;const g=O(h);if(g.width===0||g.height===0)return null;const u=h.parentElement,x=u?window.getComputedStyle(u):null;return x?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:h,rect:{x:g.left+window.scrollX,y:g.top+window.scrollY,width:g.width,height:g.height},styles:{backgroundColor:"transparent",opacity:parseFloat(x.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:p,textStyles:z(x),dirtyMask:e,children:[]}:null}const t=n,i=t.getBoundingClientRect(),r=window.getComputedStyle(t);if(i.width===0||i.height===0||r.display==="none")return null;let s=t.getAttribute("data-mid");s||(s=Math.random().toString(36).substring(2,11),t.setAttribute("data-mid",s));const T=parseInt(r.zIndex),l={backgroundColor:r.backgroundColor,opacity:parseFloat(r.opacity),zIndex:isNaN(T)?0:T,borderRadius:r.borderRadius,borderColor:r.borderColor,borderWidth:r.borderWidth};let w,a;const c=[];return Array.from(t.childNodes).forEach(h=>{const p=v(h,e);p&&c.push(p)}),{id:s,type:"BOX",element:t,rect:{x:i.left+window.scrollX,y:i.top+window.scrollY,width:i.width,height:i.height},styles:l,textContent:w,textStyles:a,dirtyMask:e,children:c}}class L{constructor(e,t){o(this,"target");o(this,"renderer");o(this,"observer");o(this,"isDomDirty",!1);o(this,"isRunning",!1);o(this,"pendingMask",S);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|=b|C,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(i=>{let r=S;for(const s of i)s.type==="childList"?r|=E:s.type==="attributes"&&(s.attributeName==="style"||s.attributeName==="class")&&(r|=b|C);if(r!==S){if(this.pendingMask|=r,r&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=v(this.target,this.pendingMask);e&&this.renderer.syncScene(e),this.pendingMask=S}}class H{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 N,this.renderer.mount(document.body),this.syncer=new L(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop(),this.renderer.dispose()}}m.Mirage=H,Object.defineProperty(m,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,13 +1,21 @@
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;
@@ -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.5",
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;