mirage-engine 0.2.6 → 0.2.7

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
@@ -1,25 +1,34 @@
1
- # MirageEngine
2
1
 
3
- > **An engine that mirrors HTML DOM elements to a WebGL scene in real-time.**
4
2
 
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)
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
8
- [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
3
+ <p align="center">
4
+ <img src="../../.github/assets/mirage-engine.png" width="300px">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/mirage-engine"><img src="https://img.shields.io/npm/v/mirage-engine.svg?color=black"></a>
9
+ <a href="https://www.npmjs.org/package/mirage-engine"><img src="https://img.shields.io/npm/dm/mirage-engine.svg?color=black"></a>
10
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg?color=black"></a>
11
+ <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-Ready-blue.svg?color=black"></a>
12
+ </p>
13
+
14
+ # MirageEngine
9
15
 
16
+ > **An engine that mirrors HTML DOM elements to a WebGL scene in real-time.**
10
17
 
11
18
  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.
12
19
 
13
20
  ## Installation
14
21
 
15
22
  ```bash
16
- npm install mirage-engine
23
+ npm install mirage-engine three
17
24
  ```
18
25
 
19
26
  ## Usage
20
27
 
28
+ ### Use simple
29
+
21
30
  ```ts
22
- import { Mirage } from 'mirage-engine';
31
+ import { Mirage } from "mirage-engine";
23
32
 
24
33
  const target = document.querySelector("#target") as HTMLElement;
25
34
 
@@ -28,4 +37,21 @@ const mirage = new Mirage(target);
28
37
  mirage.start();
29
38
  ```
30
39
 
31
- **License | MIT © dltldn333**
40
+ ### Use option
41
+
42
+ ```ts
43
+ import { Mirage } from "mirage-engine";
44
+
45
+ const target = document.querySelector("#target") as HTMLElement;
46
+ const container = document.querySelector("#container") as HTMLElement;
47
+
48
+ const mirage = new Mirage(target, {
49
+ textQuality: "low", //default is "medium" (== 2) ("low" | "medium" | "high" | number;)
50
+ mode: "duplicate", //default is "overaly" ("overlay" | "duplicate")
51
+ container: container, //The container option is only available in "duplicate" mode.
52
+ });
53
+
54
+ mirage.start();
55
+ ```
56
+
57
+ **License | MIT © dltldn333**
@@ -1,61 +1,196 @@
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) {
1
+ var B = Object.defineProperty;
2
+ var z = (r, t, e) => t in r ? B(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var o = (r, t, e) => (z(r, typeof t != "symbol" ? t + "" : t, e), e);
4
+ import * as h from "three";
5
+ const v = 0, R = 1, b = 2, D = 4, C = 8, E = 16;
6
+ function F(r, t, e) {
7
7
  const i = t.split(`
8
8
  `), s = [];
9
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];
10
+ const a = n.split(" ");
11
+ let c = a[0];
12
+ for (let u = 1; u < a.length; u++) {
13
+ const l = a[u];
14
14
  r.measureText(c + " " + l).width < e ? c += " " + l : (s.push(c), c = l);
15
15
  }
16
16
  s.push(c);
17
17
  }), s;
18
18
  }
19
- function X(r, t, e, i, s = 2) {
20
- const n = document.createElement("canvas"), o = n.getContext("2d");
21
- if (!o)
19
+ function S(r, t, e, i, s = 2) {
20
+ const n = document.createElement("canvas"), a = n.getContext("2d");
21
+ if (!a)
22
22
  throw new Error("[Mirage] Failed to create canvas context");
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);
23
+ const u = (window.devicePixelRatio || 1) * s;
24
+ n.width = e * u, n.height = i * u, a.scale(u, u), a.font = t.font, a.fillStyle = t.color, a.textBaseline = "top", a.globalAlpha = 1;
25
+ const l = F(a, r, e), p = t.lineHeight;
26
+ l.forEach((m, g) => {
27
+ const y = g * p + 2;
28
+ let f = 0;
29
+ t.textAlign === "center" ? f = e / 2 : t.textAlign === "right" && (f = e), a.textAlign = t.textAlign, a.fillText(m, f, y);
30
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;
31
+ const d = new h.CanvasTexture(n);
32
+ return d.colorSpace = h.SRGBColorSpace, d.minFilter = h.LinearFilter, d.magFilter = h.LinearFilter, d.needsUpdate = !0, d;
33
33
  }
34
- class _ {
34
+ function w(r) {
35
+ return typeof r == "number" ? r : parseFloat(r) || 0;
36
+ }
37
+ const O = `
38
+ varying vec2 vUv;
39
+ void main() {
40
+ vUv = uv;
41
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
42
+ }
43
+ `, A = `
44
+ varying vec2 vUv;
45
+
46
+ uniform vec2 uSize;
47
+ uniform float uRadius;
48
+ uniform float uBorderWidth;
49
+ uniform vec3 uColor;
50
+ uniform vec3 uBorderColor;
51
+ uniform float uOpacity;
52
+ uniform float uBgOpacity;
53
+
54
+ // SDF 박스 함수
55
+ float sdRoundedBox(vec2 p, vec2 b, float r) {
56
+ vec2 q = abs(p) - b + r;
57
+ return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
58
+ }
59
+
60
+ void main() {
61
+ vec2 p = (vUv - 0.5) * uSize;
62
+ vec2 halfSize = uSize * 0.5;
63
+
64
+ float d = sdRoundedBox(p, halfSize, uRadius);
65
+
66
+ float smoothEdge = 1.0;
67
+
68
+ float fillAlpha = 1.0 - smoothstep(-uBorderWidth - smoothEdge, -uBorderWidth, d);
69
+
70
+ float borderAlpha = 0.0;
71
+
72
+ if (uBorderWidth > 0.01) {
73
+ borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
74
+ }
75
+
76
+ vec3 color = uColor;
77
+ float totalAlpha = borderAlpha + fillAlpha;
78
+
79
+ if (totalAlpha > 0.001) {
80
+ color = mix(uColor, uBorderColor, borderAlpha / totalAlpha);
81
+ }
82
+
83
+ float shapeAlpha = borderAlpha + (fillAlpha * uBgOpacity);
84
+ float finalOpacity = shapeAlpha * uOpacity;
85
+
86
+ if (finalOpacity < 0.001) discard;
87
+
88
+ gl_FragColor = vec4(color, finalOpacity);
89
+ }
90
+ `;
91
+ function x(r) {
92
+ if (!r || r === "transparent")
93
+ return { color: new h.Color(16777215), alpha: 0 };
94
+ const t = r.match(
95
+ /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
96
+ );
97
+ if (t) {
98
+ const e = parseInt(t[1], 10), i = parseInt(t[2], 10), s = parseInt(t[3], 10), n = t[4] !== void 0 ? parseFloat(t[4]) : 1;
99
+ return { color: new h.Color(`rgb(${e}, ${i}, ${s})`), alpha: n };
100
+ }
101
+ return { color: new h.Color(r), alpha: 1 };
102
+ }
103
+ function I(r, t, e) {
104
+ const i = x(r.backgroundColor), s = x(r.borderColor), n = {
105
+ uSize: { value: new h.Vector2(t, e) },
106
+ uRadius: { value: w(r.borderRadius) },
107
+ uBorderWidth: { value: w(r.borderWidth) },
108
+ uColor: { value: i.color },
109
+ uBorderColor: { value: s.color },
110
+ uOpacity: { value: r.opacity ?? 1 },
111
+ uBgOpacity: { value: i.alpha }
112
+ };
113
+ return new h.ShaderMaterial({
114
+ uniforms: n,
115
+ vertexShader: O,
116
+ fragmentShader: A,
117
+ transparent: !0,
118
+ side: h.FrontSide
119
+ // for better performance
120
+ });
121
+ }
122
+ function L(r, t, e, i) {
123
+ const s = x(t.backgroundColor), n = x(t.borderColor);
124
+ r.uniforms.uSize.value.set(e, i), r.uniforms.uRadius.value = w(t.borderRadius), r.uniforms.uBorderWidth.value = w(t.borderWidth), r.uniforms.uColor.value.copy(s.color), r.uniforms.uBorderColor.value.copy(n.color), r.uniforms.uOpacity.value = t.opacity ?? 1, r.uniforms.uBgOpacity.value = s.alpha;
125
+ }
126
+ const T = {
127
+ create(r, t, e, i, s, n = 2) {
128
+ if (r === "BOX")
129
+ return I(t, i, s);
130
+ if (r === "TEXT") {
131
+ const a = S(
132
+ e || "",
133
+ t,
134
+ i,
135
+ s,
136
+ n
137
+ );
138
+ return new h.MeshBasicMaterial({
139
+ map: a,
140
+ transparent: !0,
141
+ side: h.FrontSide,
142
+ color: 16777215
143
+ });
144
+ }
145
+ return new h.MeshBasicMaterial({ visible: !1 });
146
+ },
147
+ update(r, t, e, i, s, n, a = 2) {
148
+ if (t === "BOX")
149
+ L(
150
+ r,
151
+ e,
152
+ s,
153
+ n
154
+ );
155
+ else if (t === "TEXT") {
156
+ const c = r;
157
+ c.map && c.map.dispose();
158
+ const u = S(
159
+ i || "",
160
+ e,
161
+ s,
162
+ n,
163
+ a
164
+ );
165
+ c.map = u, c.needsUpdate = !0;
166
+ }
167
+ }
168
+ };
169
+ class X {
35
170
  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();
171
+ o(this, "canvas");
172
+ o(this, "scene");
173
+ o(this, "camera");
174
+ o(this, "renderer");
175
+ o(this, "renderOrder", 0);
176
+ o(this, "textQualityFactor", 2);
177
+ o(this, "mode", "overlay");
178
+ o(this, "customZIndex", "9999");
179
+ o(this, "target");
180
+ o(this, "mountContainer");
181
+ o(this, "targetRect");
182
+ o(this, "meshMap", /* @__PURE__ */ new Map());
183
+ var a;
184
+ this.target = t, this.mountContainer = i, this.mode = e.mode ?? "overlay", (a = e.style) != null && a.zIndex && (this.customZIndex = e.style.zIndex), this.canvas = document.createElement("canvas"), this.scene = new h.Scene(), this.targetRect = this.target.getBoundingClientRect();
50
185
  const s = this.targetRect.width, n = this.targetRect.height;
51
- this.camera = new g.OrthographicCamera(
186
+ this.camera = new h.OrthographicCamera(
52
187
  s / -2,
53
188
  s / 2,
54
189
  n / 2,
55
190
  n / -2,
56
191
  1,
57
192
  1e3
58
- ), this.camera.position.z = 100, this.renderer = new g.WebGLRenderer({
193
+ ), this.camera.position.z = 100, this.renderer = new h.WebGLRenderer({
59
194
  canvas: this.canvas,
60
195
  alpha: !0,
61
196
  antialias: !0
@@ -96,15 +231,21 @@ class _ {
96
231
  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
232
  const n = /* @__PURE__ */ new Set();
98
233
  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));
234
+ for (const [a, c] of this.meshMap.entries())
235
+ n.has(a) || (this.scene.remove(c), c.geometry.dispose(), c.material instanceof h.Material && c.material.dispose(), this.meshMap.delete(a));
101
236
  }
102
237
  reconcileNode(t, e) {
103
238
  e.add(t.element);
104
239
  let i = this.meshMap.get(t.element);
105
240
  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);
241
+ const s = new h.PlaneGeometry(1, 1), n = T.create(
242
+ "BOX",
243
+ t.styles,
244
+ "",
245
+ t.rect.width,
246
+ t.rect.height
247
+ );
248
+ i = new h.Mesh(s, n), t.type === "TEXT" && (i.name = "BG_MESH"), this.scene.add(i), this.meshMap.set(t.element, i);
108
249
  }
109
250
  if (i.userData.domRect = t.rect, this.updateMeshProperties(i, t), t.type === "BOX")
110
251
  for (const s of t.children)
@@ -113,62 +254,52 @@ class _ {
113
254
  t.type === "TEXT" && this.reconcileTextChild(i, t);
114
255
  }
115
256
  reconcileTextChild(t, e) {
116
- var c, d;
257
+ var c, u;
117
258
  let i = t.children.find(
118
259
  (l) => l.name === "TEXT_CHILD"
119
260
  );
120
261
  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 || "",
262
+ if (!i || e.dirtyMask & E || s !== n) {
263
+ i && ((u = i.material.map) == null || u.dispose(), i.geometry.dispose(), t.remove(i));
264
+ const l = T.create(
265
+ "TEXT",
125
266
  e.textStyles,
267
+ e.textContent || "",
126
268
  e.rect.width,
127
269
  e.rect.height,
128
270
  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);
271
+ ), p = new h.PlaneGeometry(1, 1);
272
+ i = new h.Mesh(p, l), i.name = "TEXT_CHILD", i.userData = { styleHash: s }, i.position.z = 5e-3, t.add(i);
136
273
  }
137
274
  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);
275
+ const l = t.userData.domRect, p = l.x + l.width / 2, d = l.y + l.height / 2, m = e.rect.x + e.rect.width / 2, g = e.rect.y + e.rect.height / 2, y = m - p, f = -(g - d);
276
+ i.position.set(y, f, 5e-3);
140
277
  }
141
278
  }
142
279
  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;
280
+ const { rect: i, styles: s } = e, n = this.renderer.getPixelRatio(), a = this.renderer.domElement.width / n, c = this.renderer.domElement.height / n;
144
281
  t.scale.set(i.width, i.height, 1);
145
- const d = 1e-3;
282
+ const u = 1e-3;
146
283
  this.renderOrder++;
147
- const l = this.targetRect.left + window.scrollX, f = this.targetRect.top + window.scrollY, h = i.x - l, y = i.y - f;
284
+ const l = this.targetRect.left + window.scrollX, p = this.targetRect.top + window.scrollY, d = i.x - l, m = i.y - p;
148
285
  t.position.set(
149
- h - o / 2 + i.width / 2,
150
- -y + c / 2 - i.height / 2,
151
- s.zIndex + this.renderOrder * d
286
+ d - a / 2 + i.width / 2,
287
+ -m + c / 2 - i.height / 2,
288
+ s.zIndex + this.renderOrder * u
289
+ ), T.update(
290
+ t.material,
291
+ "BOX",
292
+ e.styles,
293
+ "",
294
+ e.rect.width,
295
+ e.rect.height
152
296
  );
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})`;
162
- }
163
- }
164
- const b = s.opacity * T;
165
- u.color.set(m), u.opacity = b, u.transparent = b < 1;
166
297
  }
167
298
  render() {
168
299
  this.renderer.render(this.scene, this.camera);
169
300
  }
170
301
  }
171
- function k(r) {
302
+ function N(r) {
172
303
  const t = document.createRange();
173
304
  t.selectNodeContents(r);
174
305
  const e = t.getBoundingClientRect();
@@ -179,7 +310,7 @@ function k(r) {
179
310
  height: e.height
180
311
  };
181
312
  }
182
- function O(r) {
313
+ function _(r) {
183
314
  const t = parseFloat(r.fontSize);
184
315
  let e = parseFloat(r.lineHeight);
185
316
  isNaN(e) && (e = t * 1.2);
@@ -194,38 +325,38 @@ function O(r) {
194
325
  letterSpacing: i
195
326
  };
196
327
  }
197
- function E(r, t = R | v | I | S | C) {
328
+ function M(r, t = R | b | D | E | C) {
198
329
  if (r.nodeType === Node.TEXT_NODE) {
199
- const h = r;
200
- if (!h.textContent || !h.textContent.trim())
330
+ const d = r;
331
+ if (!d.textContent || !d.textContent.trim())
201
332
  return null;
202
- const y = h.textContent.replace(/\s+/g, " ").trim();
203
- if (y.length === 0)
333
+ const m = d.textContent.replace(/\s+/g, " ").trim();
334
+ if (m.length === 0)
204
335
  return null;
205
- const u = k(h);
206
- if (u.width === 0 || u.height === 0)
336
+ const g = N(d);
337
+ if (g.width === 0 || g.height === 0)
207
338
  return null;
208
- const p = h.parentElement, m = p ? window.getComputedStyle(p) : null;
209
- return m ? {
339
+ const y = d.parentElement, f = y ? window.getComputedStyle(y) : null;
340
+ return f ? {
210
341
  id: Math.random().toString(36).substring(2, 9),
211
342
  type: "TEXT",
212
- element: h,
343
+ element: d,
213
344
  rect: {
214
- x: u.left + window.scrollX,
215
- y: u.top + window.scrollY,
216
- width: u.width,
217
- height: u.height
345
+ x: g.left + window.scrollX,
346
+ y: g.top + window.scrollY,
347
+ width: g.width,
348
+ height: g.height
218
349
  },
219
350
  styles: {
220
351
  backgroundColor: "transparent",
221
- opacity: parseFloat(m.opacity),
352
+ opacity: parseFloat(f.opacity),
222
353
  zIndex: 0,
223
354
  borderRadius: "0px",
224
355
  borderColor: "transparent",
225
356
  borderWidth: "0px"
226
357
  },
227
- textContent: y,
228
- textStyles: O(m),
358
+ textContent: m,
359
+ textStyles: _(f),
229
360
  dirtyMask: t,
230
361
  children: []
231
362
  } : null;
@@ -235,19 +366,19 @@ function E(r, t = R | v | I | S | C) {
235
366
  return null;
236
367
  let n = e.getAttribute("data-mid");
237
368
  n || (n = Math.random().toString(36).substring(2, 11), e.setAttribute("data-mid", n));
238
- const o = parseInt(s.zIndex), c = {
369
+ const a = parseInt(s.zIndex), c = {
239
370
  backgroundColor: s.backgroundColor,
240
371
  opacity: parseFloat(s.opacity),
241
- zIndex: isNaN(o) ? 0 : o,
372
+ zIndex: isNaN(a) ? 0 : a,
242
373
  borderRadius: s.borderRadius,
243
374
  borderColor: s.borderColor,
244
375
  borderWidth: s.borderWidth
245
376
  };
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);
377
+ let u, l;
378
+ const p = [];
379
+ return Array.from(e.childNodes).forEach((d) => {
380
+ const m = M(d, t);
381
+ m && p.push(m);
251
382
  }), {
252
383
  id: n,
253
384
  type: "BOX",
@@ -259,38 +390,38 @@ function E(r, t = R | v | I | S | C) {
259
390
  height: i.height
260
391
  },
261
392
  styles: c,
262
- textContent: d,
393
+ textContent: u,
263
394
  textStyles: l,
264
395
  dirtyMask: t,
265
- children: f
396
+ children: p
266
397
  };
267
398
  }
268
- class Y {
399
+ class k {
269
400
  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(() => {
401
+ o(this, "target");
402
+ o(this, "renderer");
403
+ o(this, "observer");
404
+ o(this, "isDomDirty", !1);
405
+ o(this, "isRunning", !1);
406
+ o(this, "pendingMask", v);
407
+ o(this, "mutationTimer", null);
408
+ o(this, "cssTimer", null);
409
+ o(this, "onTransitionFinished", (t) => {
410
+ this.target.contains(t.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= R | b, this.cssTimer = window.setTimeout(() => {
280
411
  this.isDomDirty = !0, this.cssTimer = null;
281
412
  }, 50));
282
413
  });
283
- a(this, "onWindowResize", () => {
414
+ o(this, "onWindowResize", () => {
284
415
  this.renderer.setSize(window.innerWidth, window.innerHeight), this.isDomDirty = !0;
285
416
  });
286
- a(this, "renderLoop", () => {
417
+ o(this, "renderLoop", () => {
287
418
  this.isRunning && (this.isDomDirty && this.forceUpdateScene(), this.renderer.render(), requestAnimationFrame(this.renderLoop));
288
419
  });
289
420
  this.target = t, this.renderer = e, this.observer = new MutationObserver((i) => {
290
- let s = x;
421
+ let s = v;
291
422
  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) {
423
+ n.type === "childList" ? s |= C : n.type === "attributes" && (n.attributeName === "style" || n.attributeName === "class") && (s |= R | b);
424
+ if (s !== v) {
294
425
  if (this.pendingMask |= s, s & C) {
295
426
  this.clearTimers(), console.log("Structural Change detected"), this.isDomDirty = !0;
296
427
  return;
@@ -317,20 +448,20 @@ class Y {
317
448
  }
318
449
  forceUpdateScene() {
319
450
  this.isDomDirty = !1;
320
- const t = E(this.target, this.pendingMask);
321
- t && this.renderer.syncScene(t), this.pendingMask = x;
451
+ const t = M(this.target, this.pendingMask);
452
+ t && this.renderer.syncScene(t), this.pendingMask = v;
322
453
  }
323
454
  }
324
- class H {
455
+ class P {
325
456
  constructor(t, e) {
326
- a(this, "renderer");
327
- a(this, "syncer");
328
- a(this, "target");
457
+ o(this, "renderer");
458
+ o(this, "syncer");
459
+ o(this, "target");
329
460
  this.target = t;
330
461
  let i;
331
462
  if (e.mode === "duplicate" ? i = e.container ?? this.target.parentElement ?? void 0 : i = this.target.parentElement ?? void 0, !i)
332
463
  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);
464
+ this.renderer = new X(this.target, e, i), this.renderer.mount(), this.syncer = new k(this.target, this.renderer);
334
465
  }
335
466
  start() {
336
467
  this.syncer.start();
@@ -342,12 +473,12 @@ class H {
342
473
  this.syncer.stop(), this.renderer.dispose();
343
474
  }
344
475
  }
345
- class P {
476
+ class Y {
346
477
  constructor(t, e = {}) {
347
- a(this, "_engine");
478
+ o(this, "_engine");
348
479
  if (!t)
349
480
  throw new Error("[Mirage] Target element is required.");
350
- this._engine = new H(t, e);
481
+ this._engine = new P(t, e);
351
482
  }
352
483
  start() {
353
484
  this._engine.start();
@@ -363,5 +494,5 @@ class P {
363
494
  }
364
495
  }
365
496
  export {
366
- P as Mirage
497
+ Y as Mirage
367
498
  };
@@ -1,2 +1,55 @@
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"})});
1
+ (function(p,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],f):(p=typeof globalThis<"u"?globalThis:p||self,f(p.MirageEngine={},p.THREE))})(this,function(p,f){"use strict";var Y=Object.defineProperty;var U=(p,f,w)=>f in p?Y(p,f,{enumerable:!0,configurable:!0,writable:!0,value:w}):p[f]=w;var o=(p,f,w)=>(U(p,typeof f!="symbol"?f+"":f,w),w);function w(r){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const e in r)if(e!=="default"){const i=Object.getOwnPropertyDescriptor(r,e);Object.defineProperty(t,e,i.get?i:{enumerable:!0,get:()=>r[e]})}}return t.default=r,Object.freeze(t)}const h=w(f),T=0,C=1,S=2,z=4,E=8,B=16;function F(r,t,e){const i=t.split(`
2
+ `),s=[];return i.forEach(n=>{const a=n.split(" ");let c=a[0];for(let u=1;u<a.length;u++){const l=a[u];r.measureText(c+" "+l).width<e?c+=" "+l:(s.push(c),c=l)}s.push(c)}),s}function O(r,t,e,i,s=2){const n=document.createElement("canvas"),a=n.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const u=(window.devicePixelRatio||1)*s;n.width=e*u,n.height=i*u,a.scale(u,u),a.font=t.font,a.fillStyle=t.color,a.textBaseline="top",a.globalAlpha=1;const l=F(a,r,e),m=t.lineHeight;l.forEach((g,y)=>{const x=y*m+2;let v=0;t.textAlign==="center"?v=e/2:t.textAlign==="right"&&(v=e),a.textAlign=t.textAlign,a.fillText(g,v,x)});const d=new h.CanvasTexture(n);return d.colorSpace=h.SRGBColorSpace,d.minFilter=h.LinearFilter,d.magFilter=h.LinearFilter,d.needsUpdate=!0,d}function b(r){return typeof r=="number"?r:parseFloat(r)||0}const A=`
3
+ varying vec2 vUv;
4
+ void main() {
5
+ vUv = uv;
6
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
7
+ }
8
+ `,I=`
9
+ varying vec2 vUv;
10
+
11
+ uniform vec2 uSize;
12
+ uniform float uRadius;
13
+ uniform float uBorderWidth;
14
+ uniform vec3 uColor;
15
+ uniform vec3 uBorderColor;
16
+ uniform float uOpacity;
17
+ uniform float uBgOpacity;
18
+
19
+ // SDF 박스 함수
20
+ float sdRoundedBox(vec2 p, vec2 b, float r) {
21
+ vec2 q = abs(p) - b + r;
22
+ return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
23
+ }
24
+
25
+ void main() {
26
+ vec2 p = (vUv - 0.5) * uSize;
27
+ vec2 halfSize = uSize * 0.5;
28
+
29
+ float d = sdRoundedBox(p, halfSize, uRadius);
30
+
31
+ float smoothEdge = 1.0;
32
+
33
+ float fillAlpha = 1.0 - smoothstep(-uBorderWidth - smoothEdge, -uBorderWidth, d);
34
+
35
+ float borderAlpha = 0.0;
36
+
37
+ if (uBorderWidth > 0.01) {
38
+ borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
39
+ }
40
+
41
+ vec3 color = uColor;
42
+ float totalAlpha = borderAlpha + fillAlpha;
43
+
44
+ if (totalAlpha > 0.001) {
45
+ color = mix(uColor, uBorderColor, borderAlpha / totalAlpha);
46
+ }
47
+
48
+ float shapeAlpha = borderAlpha + (fillAlpha * uBgOpacity);
49
+ float finalOpacity = shapeAlpha * uOpacity;
50
+
51
+ if (finalOpacity < 0.001) discard;
52
+
53
+ gl_FragColor = vec4(color, finalOpacity);
54
+ }
55
+ `;function R(r){if(!r||r==="transparent")return{color:new h.Color(16777215),alpha:0};const t=r.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(t){const e=parseInt(t[1],10),i=parseInt(t[2],10),s=parseInt(t[3],10),n=t[4]!==void 0?parseFloat(t[4]):1;return{color:new h.Color(`rgb(${e}, ${i}, ${s})`),alpha:n}}return{color:new h.Color(r),alpha:1}}function L(r,t,e){const i=R(r.backgroundColor),s=R(r.borderColor),n={uSize:{value:new h.Vector2(t,e)},uRadius:{value:b(r.borderRadius)},uBorderWidth:{value:b(r.borderWidth)},uColor:{value:i.color},uBorderColor:{value:s.color},uOpacity:{value:r.opacity??1},uBgOpacity:{value:i.alpha}};return new h.ShaderMaterial({uniforms:n,vertexShader:A,fragmentShader:I,transparent:!0,side:h.FrontSide})}function X(r,t,e,i){const s=R(t.backgroundColor),n=R(t.borderColor);r.uniforms.uSize.value.set(e,i),r.uniforms.uRadius.value=b(t.borderRadius),r.uniforms.uBorderWidth.value=b(t.borderWidth),r.uniforms.uColor.value.copy(s.color),r.uniforms.uBorderColor.value.copy(n.color),r.uniforms.uOpacity.value=t.opacity??1,r.uniforms.uBgOpacity.value=s.alpha}const M={create(r,t,e,i,s,n=2){if(r==="BOX")return L(t,i,s);if(r==="TEXT"){const a=O(e||"",t,i,s,n);return new h.MeshBasicMaterial({map:a,transparent:!0,side:h.FrontSide,color:16777215})}return new h.MeshBasicMaterial({visible:!1})},update(r,t,e,i,s,n,a=2){if(t==="BOX")X(r,e,s,n);else if(t==="TEXT"){const c=r;c.map&&c.map.dispose();const u=O(i||"",e,s,n,a);c.map=u,c.needsUpdate=!0}}};class N{constructor(t,e,i){o(this,"canvas");o(this,"scene");o(this,"camera");o(this,"renderer");o(this,"renderOrder",0);o(this,"textQualityFactor",2);o(this,"mode","overlay");o(this,"customZIndex","9999");o(this,"target");o(this,"mountContainer");o(this,"targetRect");o(this,"meshMap",new Map);var a;this.target=t,this.mountContainer=i,this.mode=e.mode??"overlay",(a=e.style)!=null&&a.zIndex&&(this.customZIndex=e.style.zIndex),this.canvas=document.createElement("canvas"),this.scene=new h.Scene,this.targetRect=this.target.getBoundingClientRect();const s=this.targetRect.width,n=this.targetRect.height;this.camera=new h.OrthographicCamera(s/-2,s/2,n/2,n/-2,1,1e3),this.camera.position.z=100,this.renderer=new h.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(s,n),this.applyTextQuality(e.textQuality??"medium")}applyTextQuality(t){if(typeof t=="number"){this.textQualityFactor=Math.max(.1,t);return}switch(t){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(t,e){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()}syncScene(t){const e=this.target.getBoundingClientRect(),i=Math.abs(e.width-this.targetRect.width)>.1||Math.abs(e.height-this.targetRect.height)>.1,s=this.mode==="overlay"&&(Math.abs(e.top-this.targetRect.top)>.1||Math.abs(e.left-this.targetRect.left)>.1);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;const n=new Set;this.reconcileNode(t,n);for(const[a,c]of this.meshMap.entries())n.has(a)||(this.scene.remove(c),c.geometry.dispose(),c.material instanceof h.Material&&c.material.dispose(),this.meshMap.delete(a))}reconcileNode(t,e){e.add(t.element);let i=this.meshMap.get(t.element);if(!i){const s=new h.PlaneGeometry(1,1),n=M.create("BOX",t.styles,"",t.rect.width,t.rect.height);i=new h.Mesh(s,n),t.type==="TEXT"&&(i.name="BG_MESH"),this.scene.add(i),this.meshMap.set(t.element,i)}if(i.userData.domRect=t.rect,this.updateMeshProperties(i,t),t.type==="BOX")for(const s of t.children)this.reconcileNode(s,e);else t.type==="TEXT"&&this.reconcileTextChild(i,t)}reconcileTextChild(t,e){var c,u;let i=t.children.find(l=>l.name==="TEXT_CHILD");const s=JSON.stringify(e.textStyles),n=(c=i==null?void 0:i.userData)==null?void 0:c.styleHash;if(!i||e.dirtyMask&B||s!==n){i&&((u=i.material.map)==null||u.dispose(),i.geometry.dispose(),t.remove(i));const l=M.create("TEXT",e.textStyles,e.textContent||"",e.rect.width,e.rect.height,this.textQualityFactor),m=new h.PlaneGeometry(1,1);i=new h.Mesh(m,l),i.name="TEXT_CHILD",i.userData={styleHash:s},i.position.z=.005,t.add(i)}if(i){const l=t.userData.domRect,m=l.x+l.width/2,d=l.y+l.height/2,g=e.rect.x+e.rect.width/2,y=e.rect.y+e.rect.height/2,x=g-m,v=-(y-d);i.position.set(x,v,.005)}}updateMeshProperties(t,e){const{rect:i,styles:s}=e,n=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/n,c=this.renderer.domElement.height/n;t.scale.set(i.width,i.height,1);const u=.001;this.renderOrder++;const l=this.targetRect.left+window.scrollX,m=this.targetRect.top+window.scrollY,d=i.x-l,g=i.y-m;t.position.set(d-a/2+i.width/2,-g+c/2-i.height/2,s.zIndex+this.renderOrder*u),M.update(t.material,"BOX",e.styles,"",e.rect.width,e.rect.height)}render(){this.renderer.render(this.scene,this.camera)}}function _(r){const t=document.createRange();t.selectNodeContents(r);const e=t.getBoundingClientRect();return{left:e.left,top:e.top,width:e.width,height:e.height}}function k(r){const t=parseFloat(r.fontSize);let e=parseFloat(r.lineHeight);isNaN(e)&&(e=t*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:e,letterSpacing:i}}function D(r,t=C|S|z|B|E){if(r.nodeType===Node.TEXT_NODE){const d=r;if(!d.textContent||!d.textContent.trim())return null;const g=d.textContent.replace(/\s+/g," ").trim();if(g.length===0)return null;const y=_(d);if(y.width===0||y.height===0)return null;const x=d.parentElement,v=x?window.getComputedStyle(x):null;return v?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:d,rect:{x:y.left+window.scrollX,y:y.top+window.scrollY,width:y.width,height:y.height},styles:{backgroundColor:"transparent",opacity:parseFloat(v.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:g,textStyles:k(v),dirtyMask:t,children:[]}:null}const e=r,i=e.getBoundingClientRect(),s=window.getComputedStyle(e);if(i.width===0||i.height===0||s.display==="none")return null;let n=e.getAttribute("data-mid");n||(n=Math.random().toString(36).substring(2,11),e.setAttribute("data-mid",n));const a=parseInt(s.zIndex),c={backgroundColor:s.backgroundColor,opacity:parseFloat(s.opacity),zIndex:isNaN(a)?0:a,borderRadius:s.borderRadius,borderColor:s.borderColor,borderWidth:s.borderWidth};let u,l;const m=[];return Array.from(e.childNodes).forEach(d=>{const g=D(d,t);g&&m.push(g)}),{id:n,type:"BOX",element:e,rect:{x:i.left+window.scrollX,y:i.top+window.scrollY,width:i.width,height:i.height},styles:c,textContent:u,textStyles:l,dirtyMask:t,children:m}}class P{constructor(t,e){o(this,"target");o(this,"renderer");o(this,"observer");o(this,"isDomDirty",!1);o(this,"isRunning",!1);o(this,"pendingMask",T);o(this,"mutationTimer",null);o(this,"cssTimer",null);o(this,"onTransitionFinished",t=>{this.target.contains(t.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=C|S,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=t,this.renderer=e,this.observer=new MutationObserver(i=>{let s=T;for(const n of i)n.type==="childList"?s|=E:n.type==="attributes"&&(n.attributeName==="style"||n.attributeName==="class")&&(s|=C|S);if(s!==T){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 t=D(this.target,this.pendingMask);t&&this.renderer.syncScene(t),this.pendingMask=T}}class H{constructor(t,e){o(this,"renderer");o(this,"syncer");o(this,"target");this.target=t;let i;if(e.mode==="duplicate"?i=e.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 N(this.target,e,i),this.renderer.mount(),this.syncer=new P(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}class W{constructor(t,e={}){o(this,"_engine");if(!t)throw new Error("[Mirage] Target element is required.");this._engine=new H(t,e)}start(){this._engine.start()}stop(){this._engine.stop()}destroy(){this._engine.dispose()}}p.Mirage=W,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})});
@@ -1,3 +1,4 @@
1
+ import { TextStyles, BoxStyles } from '../../packages/painter/src/index.ts';
1
2
  export type NodeType = "BOX" | "TEXT";
2
3
  export interface NodeRect {
3
4
  x: number;
@@ -5,23 +6,6 @@ export interface NodeRect {
5
6
  width: number;
6
7
  height: number;
7
8
  }
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
9
  export interface SceneNode {
26
10
  id: string;
27
11
  type: NodeType;
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vite').UserConfig;
2
+ export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirage-engine",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
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",
@@ -20,10 +20,8 @@
20
20
  "files": [
21
21
  "dist"
22
22
  ],
23
- "scripts": {
24
- "dev": "vite",
25
- "build": "tsc && vite build",
26
- "prepublishOnly": "npm run build"
23
+ "dependencies": {
24
+ "@mirage-engine/painter": "0.1.0"
27
25
  },
28
26
  "peerDependencies": {
29
27
  "three": "^0.150.0"
@@ -42,6 +40,15 @@
42
40
  "threejs",
43
41
  "typescript"
44
42
  ],
43
+ "private": false,
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "registry": "https://registry.npmjs.org/"
47
+ },
45
48
  "author": "dltldn333@gmail.com",
46
- "license": "MIT"
47
- }
49
+ "license": "MIT",
50
+ "scripts": {
51
+ "dev": "vite",
52
+ "build": "tsc && vite build"
53
+ }
54
+ }
@@ -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, qualityFactor?: number): THREE.CanvasTexture;