mirage-engine 0.2.15 → 0.2.16

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.
@@ -1,71 +1,73 @@
1
- var k = Object.defineProperty;
2
- var I = (r, e, t) => e in r ? k(r, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[e] = t;
3
- var c = (r, e, t) => (I(r, typeof e != "symbol" ? e + "" : e, t), t);
1
+ var X = Object.defineProperty;
2
+ var P = (i, e, t) => e in i ? X(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
3
+ var h = (i, e, t) => (P(i, typeof e != "symbol" ? e + "" : e, t), t);
4
4
  import * as d from "three";
5
- const T = 0, S = 1, B = 2, _ = 4, M = 8, z = 16, D = 0, F = 30, A = [
5
+ const E = 0, O = 1, z = 2, q = 4, D = 8, _ = 16, x = 1, R = 2, k = [
6
6
  "include-tree",
7
7
  "exclude-tree",
8
8
  "include-self",
9
9
  "exclude-self",
10
10
  "end"
11
11
  ];
12
- function P(r, e, t) {
13
- const i = e.split(`
12
+ function Y(i, e, t) {
13
+ const r = e.split(`
14
14
  `), s = [];
15
- return i.forEach((n) => {
15
+ return r.forEach((n) => {
16
16
  const a = n.split(" ");
17
17
  let o = a[0];
18
- for (let h = 1; h < a.length; h++) {
19
- const l = a[h];
20
- r.measureText(o + " " + l).width < t ? o += " " + l : (s.push(o), o = l);
18
+ for (let l = 1; l < a.length; l++) {
19
+ const c = a[l];
20
+ i.measureText(o + " " + c).width < t ? o += " " + c : (s.push(o), o = c);
21
21
  }
22
22
  s.push(o);
23
23
  }), s;
24
24
  }
25
- function L(r, e, t, i, s = 2) {
25
+ function I(i, e, t, r, s = 2) {
26
26
  const n = document.createElement("canvas"), a = n.getContext("2d");
27
27
  if (!a)
28
28
  throw new Error("[Mirage] Failed to create canvas context");
29
- const h = (window.devicePixelRatio || 1) * s;
30
- n.width = t * h, n.height = i * h, a.scale(h, h), a.font = e.font, a.fillStyle = e.color, a.textBaseline = "top", a.globalAlpha = 1;
31
- const l = P(a, r, t), p = e.lineHeight;
32
- l.forEach((x, w) => {
33
- const b = w * p + 2;
34
- let y = 0;
35
- e.textAlign === "center" ? y = t / 2 : e.textAlign === "right" && (y = t), a.textAlign = e.textAlign, a.fillText(x, y, b);
29
+ const l = (window.devicePixelRatio || 1) * s;
30
+ n.width = t * l, n.height = r * l, a.scale(l, l), a.font = e.font, a.fillStyle = e.color, a.textBaseline = "top", a.globalAlpha = 1;
31
+ const c = Y(a, i, t), g = e.lineHeight;
32
+ c.forEach((p, m) => {
33
+ const T = m * g + 2;
34
+ let v = 0;
35
+ e.textAlign === "center" ? v = t / 2 : e.textAlign === "right" && (v = t), a.textAlign = e.textAlign, a.fillText(p, v, T);
36
36
  });
37
- const f = new d.CanvasTexture(n);
38
- return f.colorSpace = d.SRGBColorSpace, f.minFilter = d.LinearFilter, f.magFilter = d.LinearFilter, f.needsUpdate = !0, f;
37
+ const u = new d.CanvasTexture(n);
38
+ return u.colorSpace = d.SRGBColorSpace, u.minFilter = d.LinearFilter, u.magFilter = d.LinearFilter, u.needsUpdate = !0, u;
39
39
  }
40
- function v(r) {
41
- return typeof r == "number" ? r : parseFloat(r) || 0;
40
+ function w(i) {
41
+ return typeof i == "number" ? i : parseFloat(i) || 0;
42
42
  }
43
- function X(r, e) {
44
- var o, h, l, p;
43
+ function N(i, e) {
44
+ var o, l, c, g;
45
45
  if (!e) {
46
- r.set(0, 0, 0, 0);
46
+ i.set(0, 0, 0, 0);
47
47
  return;
48
48
  }
49
- const t = e.split("/")[0].trim().split(/\s+/), i = v(t[0]), s = v((o = t[1]) != null ? o : t[0]), n = v((h = t[2]) != null ? h : t[0]), a = v((p = (l = t[3]) != null ? l : t[1]) != null ? p : t[0]);
50
- r.set(i, s, n, a);
49
+ const t = e.split("/")[0].trim().split(/\s+/), r = w(t[0]), s = w((o = t[1]) != null ? o : t[0]), n = w((l = t[2]) != null ? l : t[0]), a = w((g = (c = t[3]) != null ? c : t[1]) != null ? g : t[0]);
50
+ i.set(r, s, n, a);
51
51
  }
52
- const W = (
52
+ const U = (
53
53
  /* glsl */
54
54
  `
55
55
  varying vec2 vUv;
56
+ varying vec4 vScreenPos;
56
57
  void main() {
57
58
  vUv = uv;
58
59
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
60
+ vScreenPos = gl_Position;
59
61
  }
60
62
  `
61
- ), Y = (
63
+ ), W = (
62
64
  /* glsl */
63
65
  `
64
66
 
65
67
  varying vec2 vUv;
66
68
 
67
69
  uniform vec2 uSize;
68
- uniform vec4 uRadius;
70
+ uniform vec4 uBorderRadius;
69
71
  uniform float uBorderWidth;
70
72
  uniform vec3 uBgColor;
71
73
  uniform vec3 uBorderColor;
@@ -73,121 +75,132 @@ const W = (
73
75
  uniform float uBgOpacity;
74
76
  uniform float uBorderOpacity;
75
77
 
76
- // SDF box
78
+ #INJECT_DECLARATIONS
79
+
77
80
  float sdRoundedBox(vec2 p, vec2 b, float r) {
78
81
  vec2 q = abs(p) - b + r;
79
- // return length(max(q, 0.0)) - r;
82
+
80
83
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
81
84
  }
82
85
 
83
86
  void main() {
84
- // uVu: (0.0, 0.0) ~ (1.0, 1.0) / center: (0.5, 0.5)
85
- // p: (-1.0, -1.0) ~ (1.0, 1.0) / center: (0.0, 0.0)
86
87
  vec2 p = (vUv - 0.5) * uSize;
87
88
  vec2 halfSize = uSize * 0.5;
89
+
90
+ #INJECT_UV_MODIFIER
88
91
 
89
- // # border-radius
90
- vec2 xRadii = mix(uRadius.xw, uRadius.yz, step(0.0, p.x));
92
+ // color decision pipeline
93
+ vec4 baseColor = vec4(uBgColor, uBgOpacity);
94
+
95
+ #INJECT_BASE_COLOR
96
+
97
+ // sdf shape pipeline
98
+ vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
91
99
  float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
92
100
  float d = sdRoundedBox(p, halfSize, r);
93
101
 
94
- // 1px blur for anti-aliasing
95
102
  float aa = 1.0;
96
-
97
- // x == 0~aa -> 1
98
- // x < 0 -> 0
99
- // x > aa -> 1
100
103
  float bgMask = 1.0 - smoothstep(0.0, aa, d);
101
104
 
102
- // v valley
103
- // 10px -> 5
104
105
  float halfBorder = uBorderWidth * 0.5;
105
- // 10px :
106
- // 0 -> 0
107
- // -10 -> 0
108
- // -5 -> -5
109
106
  float borderD = abs(d + halfBorder) - halfBorder;
110
- // ^ border 내부는 음수/외부는 양수
111
-
112
- // [!] 내부가 음수인 곳에서 시작점이 0이면 내부는 안티 얼리어싱 되지 않음.
113
- // 1에서 빼든 그대로든 상관없이 경계선 밖 1px
114
-
115
- // 최종 alpha를 위해 border 부분에 해당하는 픽셀 만 1로 반환.
116
107
  float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
117
-
118
108
  if (uBorderWidth <= 0.01) {
119
109
  borderAlpha = 0.0;
120
110
  }
121
111
 
112
+ // final blending (border + background)
122
113
  float aFront = borderAlpha;
123
- float aBack = uBgOpacity;
114
+ float aBack = baseColor.a;
124
115
  float aOut = aFront + aBack * (1.0 - aFront);
125
116
 
126
117
  float safeAlpha = max(aOut, 0.0001);
127
- vec3 cOut = (uBorderColor * aFront + uBgColor * aBack * (1.0 - aFront)) / safeAlpha;
118
+ vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
119
+ vec4 finalColor = vec4(cOut, aOut);
128
120
 
129
- float finalOpacity = aOut * bgMask * uOpacity;
121
+ // final color control (Tint, Noise)
122
+ #INJECT_COLOR_MODIFIER
130
123
 
124
+ float finalOpacity = finalColor.a * bgMask * uOpacity;
131
125
  if (finalOpacity < 0.001) discard;
132
126
 
133
- gl_FragColor = vec4(cOut, finalOpacity);
127
+ gl_FragColor = vec4(finalColor.rgb, finalOpacity);
134
128
 
135
129
  #include <colorspace_fragment>
136
130
  }
137
131
  `
138
132
  );
139
- function R(r) {
140
- if (!r || r === "transparent")
133
+ function S(i) {
134
+ if (!i || i === "transparent")
141
135
  return { color: new d.Color(16777215), alpha: 0 };
142
- const e = r.match(
136
+ const e = i.match(
143
137
  /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
144
138
  );
145
139
  if (e) {
146
- const t = parseInt(e[1], 10), i = parseInt(e[2], 10), s = parseInt(e[3], 10), n = e[4] !== void 0 ? parseFloat(e[4]) : 1;
147
- return { color: new d.Color(`rgb(${t}, ${i}, ${s})`), alpha: n };
140
+ const t = parseInt(e[1], 10), r = parseInt(e[2], 10), s = parseInt(e[3], 10), n = e[4] !== void 0 ? parseFloat(e[4]) : 1;
141
+ return { color: new d.Color(`rgb(${t}, ${r}, ${s})`), alpha: n };
148
142
  }
149
- return { color: new d.Color(r), alpha: 1 };
143
+ return { color: new d.Color(i), alpha: 1 };
150
144
  }
151
- function H(r, e, t) {
152
- var o;
153
- const i = R(r.backgroundColor), s = R(r.borderColor), n = {
145
+ function H(i, e, t, r = null, s) {
146
+ var v;
147
+ const n = r !== null, a = n ? (
148
+ /* glsl */
149
+ `
150
+ uniform sampler2D uTexture;
151
+ varying vec4 vScreenPos;
152
+ `
153
+ ) : "", o = n ? (
154
+ /* glsl */
155
+ `
156
+ vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
157
+ vec2 resultUv = screenUv;
158
+ ${(s == null ? void 0 : s.uvModifier) || ""}
159
+ `
160
+ ) : "", l = n ? (
161
+ /* glsl */
162
+ `
163
+ baseColor = texture2D(uTexture, resultUv);
164
+ `
165
+ ) : "", c = (s == null ? void 0 : s.colorModifier) || "", g = W.replace("#INJECT_DECLARATIONS", a).replace("#INJECT_UV_MODIFIER", o).replace("#INJECT_BASE_COLOR", l).replace("#INJECT_COLOR_MODIFIER", c), u = S(i.backgroundColor), p = S(i.borderColor), m = {
154
166
  uSize: { value: new d.Vector2(e, t) },
155
- uRadius: { value: new d.Vector4(0, 0, 0, 0) },
156
- uBorderWidth: { value: v(r.borderWidth) },
157
- uBgColor: { value: i.color },
158
- uBorderColor: { value: s.color },
159
- uOpacity: { value: (o = r.opacity) != null ? o : 1 },
160
- uBgOpacity: { value: i.alpha },
161
- uBorderOpacity: { value: s.alpha }
167
+ uBorderRadius: { value: new d.Vector4(0, 0, 0, 0) },
168
+ uBorderWidth: { value: w(i.borderWidth) },
169
+ uBgColor: { value: u.color },
170
+ uBorderColor: { value: p.color },
171
+ uOpacity: { value: (v = i.opacity) != null ? v : 1 },
172
+ uBgOpacity: { value: u.alpha },
173
+ uBorderOpacity: { value: p.alpha },
174
+ uTexture: { value: null }
162
175
  };
163
- return X(n.uRadius.value, r.borderRadius), new d.ShaderMaterial({
164
- uniforms: n,
165
- vertexShader: W,
166
- fragmentShader: Y,
176
+ return N(m.uBorderRadius.value, i.borderRadius), n && (m.uTexture.value = r), new d.ShaderMaterial({
177
+ uniforms: m,
178
+ vertexShader: U,
179
+ fragmentShader: g,
167
180
  transparent: !0,
168
181
  side: d.FrontSide
169
182
  // for better performance
170
183
  });
171
184
  }
172
- function U(r, e, t, i) {
173
- var a;
174
- const s = R(e.backgroundColor), n = R(e.borderColor);
175
- r.uniforms.uSize.value.set(t, i), X(r.uniforms.uRadius.value, e.borderRadius), r.uniforms.uBorderWidth.value = v(e.borderWidth), r.uniforms.uBgColor.value.copy(s.color), r.uniforms.uBorderColor.value.copy(n.color), r.uniforms.uOpacity.value = (a = e.opacity) != null ? a : 1, r.uniforms.uBgOpacity.value = s.alpha, r.uniforms.uBorderOpacity.value = n.alpha;
185
+ function $(i, e, t, r, s) {
186
+ var o;
187
+ const n = S(e.backgroundColor), a = S(e.borderColor);
188
+ i.uniforms.uSize.value.set(t, r), N(i.uniforms.uBorderRadius.value, e.borderRadius), i.uniforms.uBorderWidth.value = w(e.borderWidth), i.uniforms.uBgColor.value.copy(n.color), i.uniforms.uBorderColor.value.copy(a.color), i.uniforms.uOpacity.value = (o = e.opacity) != null ? o : 1, i.uniforms.uBgOpacity.value = n.alpha, i.uniforms.uBorderOpacity.value = a.alpha, i.uniforms.uTexture && s !== void 0 && (i.uniforms.uTexture.value = s);
176
189
  }
177
- const E = {
178
- create(r, e, t, i, s, n = 2) {
179
- if (r === "BOX")
180
- return H(e, i, s);
181
- if (r === "TEXT") {
182
- const a = L(
190
+ const F = {
191
+ create(i, e, t, r, s, n = 2, a = null) {
192
+ if (i === "BOX")
193
+ return H(e, r, s, a);
194
+ if (i === "TEXT") {
195
+ const o = I(
183
196
  t || "",
184
197
  e,
185
- i,
198
+ r,
186
199
  s,
187
200
  n
188
201
  );
189
202
  return new d.MeshBasicMaterial({
190
- map: a,
203
+ map: o,
191
204
  transparent: !0,
192
205
  side: d.FrontSide,
193
206
  color: 16777215
@@ -195,43 +208,45 @@ const E = {
195
208
  }
196
209
  return new d.MeshBasicMaterial({ visible: !1 });
197
210
  },
198
- update(r, e, t, i, s, n, a = 2) {
211
+ update(i, e, t, r, s, n, a = 2, o) {
199
212
  if (e === "BOX")
200
- U(
201
- r,
213
+ $(
214
+ i,
202
215
  t,
203
216
  s,
204
- n
217
+ n,
218
+ o
205
219
  );
206
220
  else if (e === "TEXT") {
207
- const o = r;
208
- o.map && o.map.dispose();
209
- const h = L(
210
- i || "",
221
+ const l = i;
222
+ l.map && l.map.dispose();
223
+ const c = I(
224
+ r || "",
211
225
  t,
212
226
  s,
213
227
  n,
214
228
  a
215
229
  );
216
- o.map = h, o.needsUpdate = !0;
230
+ l.map = c, l.needsUpdate = !0;
217
231
  }
218
232
  }
219
233
  };
220
- class $ {
221
- constructor(e, t, i) {
222
- c(this, "canvas");
223
- c(this, "scene");
224
- c(this, "camera");
225
- c(this, "renderer");
226
- c(this, "renderOrder", 0);
227
- c(this, "textQualityFactor", 2);
228
- c(this, "mode", "overlay");
229
- c(this, "target");
230
- c(this, "mountContainer");
231
- c(this, "targetRect");
232
- c(this, "meshMap", /* @__PURE__ */ new Map());
234
+ class G {
235
+ constructor(e, t, r) {
236
+ h(this, "canvas");
237
+ h(this, "scene");
238
+ h(this, "camera");
239
+ h(this, "renderer");
240
+ h(this, "renderTarget", null);
241
+ h(this, "renderOrder", 0);
242
+ h(this, "qualityFactor", 2);
243
+ h(this, "mode", "overlay");
244
+ h(this, "target");
245
+ h(this, "mountContainer");
246
+ h(this, "targetRect");
247
+ h(this, "meshMap", /* @__PURE__ */ new Map());
233
248
  var a, o;
234
- this.target = e, this.mountContainer = i, this.mode = (a = t.mode) != null ? a : "overlay", this.canvas = document.createElement("canvas"), this.scene = new d.Scene(), this.targetRect = this.target.getBoundingClientRect();
249
+ this.target = e, this.mountContainer = r, this.mode = (a = t.mode) != null ? a : "overlay", this.canvas = document.createElement("canvas"), this.scene = new d.Scene(), this.targetRect = this.target.getBoundingClientRect();
235
250
  const s = this.targetRect.width, n = this.targetRect.height;
236
251
  this.camera = new d.OrthographicCamera(
237
252
  s / -2,
@@ -240,29 +255,43 @@ class $ {
240
255
  n / -2,
241
256
  1,
242
257
  1e3
243
- ), this.camera.position.z = 100, this.renderer = new d.WebGLRenderer({
258
+ ), this.camera.position.z = 100, this.camera.layers.set(0), this.renderer = new d.WebGLRenderer({
244
259
  canvas: this.canvas,
245
260
  alpha: !0,
246
261
  antialias: !0
247
262
  // [new]
248
263
  // premultipliedAlpha: true
249
- }), this.renderer.setPixelRatio(window.devicePixelRatio), this.renderer.setSize(s, n), this.applyTextQuality((o = t.textQuality) != null ? o : "medium");
264
+ }), this.renderer.setPixelRatio(window.devicePixelRatio), this.renderer.setSize(s, n), this.applyTextQuality((o = t.quality) != null ? o : "medium");
265
+ }
266
+ createRenderTarget() {
267
+ this.renderTarget = new d.WebGLRenderTarget(
268
+ this.targetRect.width * this.qualityFactor,
269
+ this.targetRect.height * this.qualityFactor,
270
+ {
271
+ minFilter: d.LinearFilter,
272
+ magFilter: d.LinearFilter,
273
+ format: d.RGBAFormat,
274
+ stencilBuffer: !1,
275
+ depthBuffer: !0,
276
+ samples: 4
277
+ }
278
+ );
250
279
  }
251
280
  applyTextQuality(e) {
252
281
  if (typeof e == "number") {
253
- this.textQualityFactor = Math.max(0.1, e);
282
+ this.qualityFactor = Math.max(0.1, e);
254
283
  return;
255
284
  }
256
285
  switch (e) {
257
286
  case "low":
258
- this.textQualityFactor = 1;
287
+ this.qualityFactor = 1;
259
288
  break;
260
289
  case "high":
261
- this.textQualityFactor = 4;
290
+ this.qualityFactor = 4;
262
291
  break;
263
292
  case "medium":
264
293
  default:
265
- this.textQualityFactor = 2;
294
+ this.qualityFactor = 2;
266
295
  break;
267
296
  }
268
297
  }
@@ -276,84 +305,126 @@ class $ {
276
305
  this.renderer.dispose(), this.canvas.remove();
277
306
  }
278
307
  setSize(e, t) {
279
- 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();
308
+ this.renderer.setSize(e, t), this.renderTarget && this.renderTarget.setSize(
309
+ e * this.qualityFactor,
310
+ t * this.qualityFactor
311
+ ), this.camera.left = e / -2, this.camera.right = e / 2, this.camera.top = t / 2, this.camera.bottom = t / -2, this.camera.updateProjectionMatrix();
280
312
  }
281
313
  syncScene(e) {
282
- const t = this.target.getBoundingClientRect(), i = Math.abs(t.width - this.targetRect.width) > 0.1 || Math.abs(t.height - this.targetRect.height) > 0.1, s = this.mode === "overlay" && (Math.abs(t.top - this.targetRect.top) > 0.1 || Math.abs(t.left - this.targetRect.left) > 0.1);
283
- 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.layers.set(0), this.camera.updateProjectionMatrix(), this.updateCanvasLayout()) : s ? (this.targetRect = t, this.updateCanvasLayout()) : this.targetRect = t, this.renderOrder = 0;
314
+ const t = this.target.getBoundingClientRect(), r = Math.abs(t.width - this.targetRect.width) > 0.1 || Math.abs(t.height - this.targetRect.height) > 0.1, s = this.mode === "overlay" && (Math.abs(t.top - this.targetRect.top) > 0.1 || Math.abs(t.left - this.targetRect.left) > 0.1);
315
+ r ? (this.targetRect = t, this.setSize(this.targetRect.width, this.targetRect.height), this.updateCanvasLayout()) : s ? (this.targetRect = t, this.updateCanvasLayout()) : this.targetRect = t, this.renderOrder = 0;
284
316
  const n = /* @__PURE__ */ new Set();
285
317
  this.reconcileNode(e, n);
286
318
  for (const [a, o] of this.meshMap.entries())
287
319
  n.has(a) || (this.scene.remove(o), o.geometry.dispose(), o.material instanceof d.Material && o.material.dispose(), this.meshMap.delete(a));
288
320
  }
289
321
  reconcileNode(e, t) {
322
+ var s;
290
323
  t.add(e.element);
291
- let i = this.meshMap.get(e.element);
292
- if (!i) {
293
- const s = new d.PlaneGeometry(1, 1), n = E.create(
324
+ let r = this.meshMap.get(e.element);
325
+ if (!r) {
326
+ const n = new d.PlaneGeometry(1, 1);
327
+ let a;
328
+ a = F.create(
294
329
  "BOX",
295
330
  e.styles,
296
331
  "",
297
332
  e.rect.width,
298
- e.rect.height
299
- );
300
- i = new d.Mesh(s, n), i.layers.set(e.visibility), e.type === "TEXT" && (i.name = "BG_MESH"), this.scene.add(i), this.meshMap.set(e.element, i);
333
+ e.rect.height,
334
+ this.qualityFactor,
335
+ e.isTraveler ? (s = this.renderTarget) == null ? void 0 : s.texture : void 0
336
+ ), r = new d.Mesh(n, a), e.type === "TEXT" && (r.name = "BG_MESH"), this.scene.add(r), this.meshMap.set(e.element, r);
301
337
  }
302
- if (i.userData.domRect = e.rect, this.updateMeshProperties(i, e), e.type === "BOX")
303
- for (const s of e.children)
304
- this.reconcileNode(s, t);
338
+ if (r.userData.domRect = e.rect, this.updateMeshProperties(r, e), this.updateMeshLayers(r, e), e.isTraveler && r.layers.enable(28), e.type === "BOX")
339
+ for (const n of e.children)
340
+ this.reconcileNode(n, t);
305
341
  else
306
- e.type === "TEXT" && this.reconcileTextChild(i, e);
342
+ e.type === "TEXT" && this.reconcileTextChild(r, e);
307
343
  }
308
344
  reconcileTextChild(e, t) {
309
- var o, h;
310
- let i = e.children.find(
311
- (l) => l.name === "TEXT_CHILD"
345
+ var o, l;
346
+ let r = e.children.find(
347
+ (c) => c.name === "TEXT_CHILD"
312
348
  );
313
- const s = JSON.stringify(t.textStyles), n = (o = i == null ? void 0 : i.userData) == null ? void 0 : o.styleHash;
314
- if (!i || t.dirtyMask & z || s !== n) {
315
- i && ((h = i.material.map) == null || h.dispose(), i.geometry.dispose(), e.remove(i));
316
- const l = E.create(
349
+ const s = JSON.stringify(t.textStyles), n = (o = r == null ? void 0 : r.userData) == null ? void 0 : o.styleHash;
350
+ if (!r || t.dirtyMask & _ || s !== n) {
351
+ r && ((l = r.material.map) == null || l.dispose(), r.geometry.dispose(), e.remove(r));
352
+ const c = F.create(
317
353
  "TEXT",
318
354
  t.textStyles,
319
355
  t.textContent || "",
320
356
  t.rect.width,
321
357
  t.rect.height,
322
- this.textQualityFactor
323
- ), p = new d.PlaneGeometry(1, 1);
324
- i = new d.Mesh(p, l), i.name = "TEXT_CHILD", i.userData = { styleHash: s }, i.layers.set(t.visibility), i.position.z = 5e-3, e.add(i);
358
+ this.qualityFactor
359
+ ), g = new d.PlaneGeometry(1, 1);
360
+ r = new d.Mesh(g, c), r.name = "TEXT_CHILD", r.userData = { styleHash: s }, this.updateMeshLayers(r, t), r.position.z = 5e-3, e.add(r);
325
361
  }
326
- if (i) {
327
- const l = e.userData.domRect, p = l.x + l.width / 2, f = l.y + l.height / 2, x = t.rect.x + t.rect.width / 2, w = t.rect.y + t.rect.height / 2, b = x - p, y = -(w - f);
328
- i.position.set(b, y, 5e-3);
362
+ if (r) {
363
+ const c = e.userData.domRect, g = c.x + c.width / 2, u = c.y + c.height / 2, p = t.rect.x + t.rect.width / 2, m = t.rect.y + t.rect.height / 2, T = p - g, v = -(m - u);
364
+ r.position.set(T, v, 5e-3);
329
365
  }
330
366
  }
331
367
  updateMeshProperties(e, t) {
332
- const { rect: i, styles: s } = t, n = this.renderer.getPixelRatio(), a = this.renderer.domElement.width / n, o = this.renderer.domElement.height / n;
333
- e.scale.set(i.width, i.height, 1);
334
- const h = 1e-3;
368
+ var m;
369
+ const { rect: r, styles: s } = t, n = this.renderer.getPixelRatio(), a = this.renderer.domElement.width / n, o = this.renderer.domElement.height / n;
370
+ e.scale.set(r.width, r.height, 1);
371
+ const l = 1e-3;
335
372
  this.renderOrder++;
336
- const l = this.targetRect.left + window.scrollX, p = this.targetRect.top + window.scrollY, f = i.x - l, x = i.y - p;
373
+ const c = this.targetRect.left + window.scrollX, g = this.targetRect.top + window.scrollY, u = r.x - c, p = r.y - g;
337
374
  e.position.set(
338
- f - a / 2 + i.width / 2,
339
- -x + o / 2 - i.height / 2,
340
- s.zIndex + this.renderOrder * h
341
- ), E.update(
375
+ u - a / 2 + r.width / 2,
376
+ -p + o / 2 - r.height / 2,
377
+ s.zIndex + this.renderOrder * l
378
+ ), F.update(
342
379
  e.material,
343
380
  "BOX",
344
381
  t.styles,
345
382
  "",
346
383
  t.rect.width,
347
- t.rect.height
384
+ t.rect.height,
385
+ this.qualityFactor,
386
+ t.isTraveler ? (m = this.renderTarget) == null ? void 0 : m.texture : void 0
348
387
  );
349
388
  }
389
+ updateMeshLayers(e, t) {
390
+ const r = (1 - (t.visibility & x)) * 30;
391
+ e.layers.set(r), t.visibility === (x | R) && e.layers.enable(29);
392
+ }
393
+ captureRenderTarget() {
394
+ const e = [];
395
+ for (const l of this.meshMap.values())
396
+ l.layers.mask & 1 << 28 && e.push(l);
397
+ if (e.length === 0)
398
+ return;
399
+ const t = new d.Color(), r = this.renderer.getClearAlpha();
400
+ this.renderer.getClearColor(t), this.renderer.setClearColor(0, 0), this.renderer.setRenderTarget(this.renderTarget), this.renderer.clear(), this.renderer.autoClear = !1, this.renderer.setScissorTest(!0), this.camera.layers.set(29);
401
+ const s = new d.Vector3(), n = this.targetRect.width, a = this.targetRect.height, o = this.renderer.getPixelRatio();
402
+ for (const l of e) {
403
+ s.setFromMatrixPosition(l.matrixWorld), s.project(this.camera);
404
+ const c = (s.x + 1) / 2 * n, g = (s.y + 1) / 2 * a, u = l.scale.x, p = l.scale.y, m = c - u / 2, T = g - p / 2, v = m * this.qualityFactor / o, B = T * this.qualityFactor / o, b = u * this.qualityFactor / o, f = p * this.qualityFactor / o;
405
+ this.renderer.setScissor(v, B, b, f), this.renderer.render(this.scene, this.camera);
406
+ }
407
+ this.renderer.setScissorTest(!1), this.renderer.autoClear = !0, this.renderer.setRenderTarget(null), this.camera.layers.set(28), this.renderer.setClearColor(t, r);
408
+ }
350
409
  render() {
351
- this.renderer.render(this.scene, this.camera);
410
+ this.renderTarget && this.captureRenderTarget(), this.renderer.render(this.scene, this.camera);
411
+ }
412
+ // for debugging
413
+ showScissoredRenderTarget() {
414
+ if (!this.renderTarget)
415
+ return;
416
+ const e = this.targetRect.width, t = this.targetRect.height, r = new d.PlaneGeometry(e, t), s = new d.MeshBasicMaterial({
417
+ map: this.renderTarget.texture,
418
+ side: d.DoubleSide,
419
+ transparent: !0,
420
+ opacity: 0.8
421
+ }), n = new d.Mesh(r, s);
422
+ n.position.set(0, 0, 90), n.layers.set(28), this.scene.add(n);
352
423
  }
353
424
  }
354
- function Q(r) {
425
+ function J(i) {
355
426
  const e = document.createRange();
356
- e.selectNodeContents(r);
427
+ e.selectNodeContents(i);
357
428
  const t = e.getBoundingClientRect();
358
429
  return {
359
430
  left: t.left,
@@ -362,152 +433,171 @@ function Q(r) {
362
433
  height: t.height
363
434
  };
364
435
  }
365
- function G(r) {
366
- const e = parseFloat(r.fontSize);
367
- let t = parseFloat(r.lineHeight);
436
+ function V(i) {
437
+ const e = parseFloat(i.fontSize);
438
+ let t = parseFloat(i.lineHeight);
368
439
  isNaN(t) && (t = e * 1.2);
369
- let i = parseFloat(r.letterSpacing);
370
- return isNaN(i) && (i = 0), {
371
- font: `${r.fontStyle} ${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,
372
- color: r.color,
373
- textAlign: r.textAlign || "start",
440
+ let r = parseFloat(i.letterSpacing);
441
+ return isNaN(r) && (r = 0), {
442
+ font: `${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,
443
+ color: i.color,
444
+ textAlign: i.textAlign || "start",
374
445
  textBaseline: "alphabetic",
375
- direction: r.direction || "inherit",
446
+ direction: i.direction || "inherit",
376
447
  lineHeight: t,
377
- letterSpacing: i
448
+ letterSpacing: r
378
449
  };
379
450
  }
380
- function N(r, e = S | B | _ | z | M, t, i) {
381
- if (r.nodeType === Node.TEXT_NODE) {
382
- const u = r;
383
- if (!u.textContent || !u.textContent.trim())
451
+ function A(i, e = O | z | q | _ | D, t, r) {
452
+ if (i.nodeType === Node.TEXT_NODE) {
453
+ const f = i;
454
+ if (!f.textContent || !f.textContent.trim())
384
455
  return null;
385
- const m = u.textContent.replace(/\s+/g, " ").trim();
386
- if (m.length === 0)
456
+ const C = f.textContent.replace(/\s+/g, " ").trim();
457
+ if (C.length === 0)
387
458
  return null;
388
- const g = Q(u);
389
- if (g.width === 0 || g.height === 0)
459
+ const y = J(f);
460
+ if (y.width === 0 || y.height === 0)
390
461
  return null;
391
- const O = u.parentElement, C = O ? window.getComputedStyle(O) : null;
392
- return C ? {
462
+ const L = f.parentElement, M = L ? window.getComputedStyle(L) : null;
463
+ return M ? {
393
464
  id: Math.random().toString(36).substring(2, 9),
394
465
  type: "TEXT",
395
- element: u,
466
+ element: f,
396
467
  rect: {
397
- x: g.left + window.scrollX,
398
- y: g.top + window.scrollY,
399
- width: g.width,
400
- height: g.height
468
+ x: y.left + window.scrollX,
469
+ y: y.top + window.scrollY,
470
+ width: y.width,
471
+ height: y.height
401
472
  },
402
473
  styles: {
403
474
  backgroundColor: "transparent",
404
- opacity: parseFloat(C.opacity),
475
+ opacity: parseFloat(M.opacity),
405
476
  zIndex: 0,
406
477
  borderRadius: "0px",
407
478
  borderColor: "transparent",
408
479
  borderWidth: "0px"
409
480
  },
410
- textContent: m,
411
- textStyles: G(C),
481
+ textContent: C,
482
+ textStyles: V(M),
412
483
  dirtyMask: e,
413
484
  visibility: t,
485
+ isTraveler: !1,
414
486
  children: []
415
487
  } : null;
416
488
  }
417
- const s = r, n = s.dataset.mirageFilter;
489
+ const s = i, n = s.dataset.mirageFilter;
418
490
  let a = t, o = t;
419
491
  if (n) {
420
- const u = new Set(n.split(/\s+/));
421
- for (const m of u)
422
- if (!A.includes(m))
492
+ const f = new Set(n.split(/\s+/));
493
+ for (const C of f)
494
+ if (!k.includes(C))
423
495
  throw new Error(
424
- `[MirageEngine] Invalid filter token: '${m}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`
496
+ `[MirageEngine] Invalid filter token: '${C}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`
425
497
  );
426
- if (u.has("end"))
498
+ if (f.has("end"))
427
499
  return null;
428
- if (u.has("include-tree") && u.has("exclude-tree"))
500
+ if (f.has("include-tree") && f.has("exclude-tree"))
429
501
  throw new Error(
430
502
  "[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element."
431
503
  );
432
- if (u.has("include-self") && u.has("exclude-self"))
504
+ if (f.has("include-self") && f.has("exclude-self"))
433
505
  throw new Error(
434
506
  "[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element."
435
507
  );
436
- u.has("include-tree") ? a = D : u.has("exclude-tree") && (a = F), o = a, u.has("include-self") ? o = D : u.has("exclude-self") && (o = F);
437
- }
438
- if (i && i.end && i.end.length > 0 && i.end.some(
439
- (m) => s.classList.contains(m)
440
- ))
441
- return null;
442
- const h = s.getBoundingClientRect(), l = window.getComputedStyle(s);
443
- if (h.width === 0 || h.height === 0 || l.display === "none")
508
+ f.has("include-tree") ? a = a | x : f.has("exclude-tree") && (a = a & ~x), o = a, f.has("include-self") ? o = o | x : f.has("exclude-self") && (o = o & ~x);
509
+ }
510
+ o = o | t & R;
511
+ const l = s.dataset.mirageTravel;
512
+ let c = !1;
513
+ l && new Set(l.split(/\s+/)).has("traveler") && (o = o & ~R, a = a & ~R, c = !0);
514
+ const g = s.getBoundingClientRect(), u = window.getComputedStyle(s);
515
+ if (g.width === 0 || g.height === 0 || u.display === "none")
444
516
  return null;
445
517
  let p = s.getAttribute("data-mid");
446
518
  p || (p = Math.random().toString(36).substring(2, 11), s.setAttribute("data-mid", p));
447
- const f = parseInt(l.zIndex), x = {
448
- backgroundColor: l.backgroundColor,
449
- opacity: parseFloat(l.opacity),
450
- zIndex: isNaN(f) ? 0 : f,
451
- borderRadius: l.borderRadius,
452
- borderColor: l.borderColor,
453
- borderWidth: l.borderWidth
519
+ const m = parseInt(u.zIndex), T = {
520
+ backgroundColor: u.backgroundColor,
521
+ opacity: parseFloat(u.opacity),
522
+ zIndex: isNaN(m) ? 0 : m,
523
+ borderRadius: u.borderRadius,
524
+ borderColor: u.borderColor,
525
+ borderWidth: u.borderWidth
454
526
  };
455
- let w, b;
456
- const y = [];
457
- return Array.from(s.childNodes).forEach((u) => {
458
- const m = u.nodeType === Node.TEXT_NODE ? o : a, g = N(
459
- u,
527
+ let v, B;
528
+ const b = [];
529
+ return Array.from(s.childNodes).forEach((f) => {
530
+ const C = f.nodeType === Node.TEXT_NODE ? o : a, y = A(
531
+ f,
460
532
  e,
461
- m,
462
- i
533
+ C
463
534
  );
464
- g && y.push(g);
535
+ y && b.push(y);
465
536
  }), {
466
537
  id: p,
467
538
  type: "BOX",
468
539
  element: s,
469
540
  rect: {
470
- x: h.left + window.scrollX,
471
- y: h.top + window.scrollY,
472
- width: h.width,
473
- height: h.height
541
+ x: g.left + window.scrollX,
542
+ y: g.top + window.scrollY,
543
+ width: g.width,
544
+ height: g.height
474
545
  },
475
- styles: x,
476
- textContent: w,
477
- textStyles: b,
546
+ styles: T,
547
+ textContent: v,
548
+ textStyles: B,
478
549
  dirtyMask: e,
479
550
  visibility: o,
480
- children: y
551
+ isTraveler: c,
552
+ children: b
481
553
  };
482
554
  }
483
- class q {
484
- constructor(e, t, i) {
485
- c(this, "target");
486
- c(this, "renderer");
487
- c(this, "filter");
488
- c(this, "observer");
489
- c(this, "isDomDirty", !1);
490
- c(this, "isRunning", !1);
491
- c(this, "pendingMask", T);
492
- c(this, "mutationTimer", null);
493
- c(this, "cssTimer", null);
494
- c(this, "onTransitionFinished", (e) => {
495
- this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= S | B, this.cssTimer = window.setTimeout(() => {
555
+ class j {
556
+ constructor(e, t, r) {
557
+ h(this, "target");
558
+ h(this, "renderer");
559
+ h(this, "filter");
560
+ h(this, "observer");
561
+ h(this, "isDomDirty", !1);
562
+ h(this, "isRunning", !1);
563
+ h(this, "isTravelEnabled", !1);
564
+ h(this, "pendingMask", E);
565
+ h(this, "mutationTimer", null);
566
+ h(this, "cssTimer", null);
567
+ h(this, "resizeConfig");
568
+ h(this, "resizeTimer", null);
569
+ h(this, "isResizing", !1);
570
+ h(this, "onTransitionFinished", (e) => {
571
+ this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= O | z, this.cssTimer = window.setTimeout(() => {
496
572
  this.isDomDirty = !0, this.cssTimer = null;
497
573
  }, 50));
498
574
  });
499
- c(this, "onWindowResize", () => {
500
- this.renderer.setSize(window.innerWidth, window.innerHeight), this.isDomDirty = !0;
575
+ h(this, "onWindowResize", () => {
576
+ if (!this.resizeConfig.enabled) {
577
+ this.isDomDirty = !0;
578
+ return;
579
+ }
580
+ this.isResizing || (this.isResizing = !0, this.resizeConfig.onStart && this.resizeConfig.onStart()), this.resizeTimer && clearTimeout(this.resizeTimer), this.resizeTimer = window.setTimeout(() => {
581
+ this.isDomDirty = !0, this.resizeConfig.onEnd && this.resizeConfig.onEnd(), this.isResizing = !1, this.resizeTimer = null;
582
+ }, this.resizeConfig.delay);
501
583
  });
502
- c(this, "renderLoop", () => {
584
+ h(this, "renderLoop", () => {
503
585
  this.isRunning && (this.isDomDirty && this.forceUpdateScene(), this.renderer.render(), requestAnimationFrame(this.renderLoop));
504
586
  });
505
- this.target = e, this.renderer = t, this.filter = i, this.observer = new MutationObserver((s) => {
506
- let n = T;
507
- for (const a of s)
508
- a.type === "childList" ? n |= M : a.type === "attributes" && (a.attributeName === "style" || a.attributeName === "class") && (n |= S | B);
509
- if (n !== T) {
510
- if (this.pendingMask |= n, n & M) {
587
+ var n, a;
588
+ this.target = e, this.renderer = t, this.filter = r.filter;
589
+ const s = (n = r.resizeDebounce) != null ? n : !0;
590
+ s === !1 ? this.resizeConfig = { enabled: !1, delay: 0 } : s === !0 ? this.resizeConfig = { enabled: !0, delay: 150 } : this.resizeConfig = {
591
+ enabled: !0,
592
+ delay: (a = s.delay) != null ? a : 150,
593
+ onStart: s.onStart,
594
+ onEnd: s.onEnd
595
+ }, this.observer = new MutationObserver((o) => {
596
+ let l = E;
597
+ for (const c of o)
598
+ c.type === "childList" ? l |= D : c.type === "attributes" && (c.attributeName === "style" || c.attributeName === "class") && (l |= O | z);
599
+ if (l !== E) {
600
+ if (this.pendingMask |= l, l & D) {
511
601
  this.clearTimers(), console.log("Structural Change detected"), this.isDomDirty = !0;
512
602
  return;
513
603
  }
@@ -533,21 +623,28 @@ class q {
533
623
  }
534
624
  forceUpdateScene() {
535
625
  this.isDomDirty = !1;
536
- const e = N(this.target, this.pendingMask, D, this.filter);
537
- e && this.renderer.syncScene(e), this.pendingMask = T;
626
+ const e = document.querySelector("[data-mirage-travel='traveler']") !== null;
627
+ e && !this.isTravelEnabled && (this.isTravelEnabled = !0, this.renderer.createRenderTarget());
628
+ const t = A(
629
+ this.target,
630
+ this.pendingMask,
631
+ e ? x | R : x,
632
+ this.filter
633
+ );
634
+ t && this.renderer.syncScene(t), this.pendingMask = E;
538
635
  }
539
636
  }
540
- class V {
637
+ class Q {
541
638
  constructor(e, t) {
542
- c(this, "renderer");
543
- c(this, "syncer");
544
- c(this, "target");
639
+ h(this, "renderer");
640
+ h(this, "syncer");
641
+ h(this, "target");
545
642
  var s, n, a;
546
643
  this.target = e;
547
- let i;
548
- if (t.mode === "duplicate" ? i = (n = (s = t.container) != null ? s : this.target.parentElement) != null ? n : void 0 : i = (a = this.target.parentElement) != null ? a : void 0, !i)
644
+ let r;
645
+ if (t.mode === "duplicate" ? r = (n = (s = t.container) != null ? s : this.target.parentElement) != null ? n : void 0 : r = (a = this.target.parentElement) != null ? a : void 0, !r)
549
646
  throw new Error("[Mirage] Cannot find a container (parent or option).");
550
- this.renderer = new $(this.target, t, i), this.renderer.mount(), this.syncer = new q(this.target, this.renderer, t.filter);
647
+ this.renderer = new G(this.target, t, r), this.renderer.mount(), this.syncer = new j(this.target, this.renderer, t);
551
648
  }
552
649
  start() {
553
650
  this.syncer.start();
@@ -559,12 +656,12 @@ class V {
559
656
  this.syncer.stop(), this.renderer.dispose();
560
657
  }
561
658
  }
562
- class Z {
659
+ class K {
563
660
  constructor(e, t) {
564
- c(this, "_engine");
661
+ h(this, "_engine");
565
662
  if (!e)
566
663
  throw new Error("[Mirage] Target element is required.");
567
- this._engine = new V(e, t);
664
+ this._engine = new Q(e, t);
568
665
  }
569
666
  start() {
570
667
  this._engine.start();
@@ -577,5 +674,5 @@ class Z {
577
674
  }
578
675
  }
579
676
  export {
580
- Z as Mirage
677
+ K as Mirage
581
678
  };
@@ -1,16 +1,18 @@
1
- (function(p,g){typeof exports=="object"&&typeof module!="undefined"?g(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],g):(p=typeof globalThis!="undefined"?globalThis:p||self,g(p.MirageEngine={},p.THREE))})(this,function(p,g){"use strict";var J=Object.defineProperty;var K=(p,g,w)=>g in p?J(p,g,{enumerable:!0,configurable:!0,writable:!0,value:w}):p[g]=w;var c=(p,g,w)=>(K(p,typeof g!="symbol"?g+"":g,w),w);function w(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=w(g),E=0,B=1,M=2,P=4,O=8,L=16,D=0,N=30,A=["include-tree","exclude-tree","include-self","exclude-self","end"];function H(r,e,t){const i=e.split(`
2
- `),s=[];return i.forEach(n=>{const a=n.split(" ");let o=a[0];for(let h=1;h<a.length;h++){const l=a[h];r.measureText(o+" "+l).width<t?o+=" "+l:(s.push(o),o=l)}s.push(o)}),s}function X(r,e,t,i,s=2){const n=document.createElement("canvas"),a=n.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const h=(window.devicePixelRatio||1)*s;n.width=t*h,n.height=i*h,a.scale(h,h),a.font=e.font,a.fillStyle=e.color,a.textBaseline="top",a.globalAlpha=1;const l=H(a,r,t),f=e.lineHeight;l.forEach((b,R)=>{const C=R*f+2;let v=0;e.textAlign==="center"?v=t/2:e.textAlign==="right"&&(v=t),a.textAlign=e.textAlign,a.fillText(b,v,C)});const m=new d.CanvasTexture(n);return m.colorSpace=d.SRGBColorSpace,m.minFilter=d.LinearFilter,m.magFilter=d.LinearFilter,m.needsUpdate=!0,m}function T(r){return typeof r=="number"?r:parseFloat(r)||0}function k(r,e){var o,h,l,f;if(!e){r.set(0,0,0,0);return}const t=e.split("/")[0].trim().split(/\s+/),i=T(t[0]),s=T((o=t[1])!=null?o:t[0]),n=T((h=t[2])!=null?h:t[0]),a=T((f=(l=t[3])!=null?l:t[1])!=null?f:t[0]);r.set(i,s,n,a)}const W=`
1
+ (function(v,y){typeof exports=="object"&&typeof module!="undefined"?y(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],y):(v=typeof globalThis!="undefined"?globalThis:v||self,y(v.MirageEngine={},v.THREE))})(this,function(v,y){"use strict";var ee=Object.defineProperty;var te=(v,y,b)=>y in v?ee(v,y,{enumerable:!0,configurable:!0,writable:!0,value:b}):v[y]=b;var h=(v,y,b)=>(te(v,typeof y!="symbol"?y+"":y,b),b);function b(i){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(i){for(const t in i)if(t!=="default"){const r=Object.getOwnPropertyDescriptor(i,t);Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>i[t]})}}return e.default=i,Object.freeze(e)}const d=b(y),M=0,F=1,z=2,k=4,D=8,N=16,C=1,E=2,Y=["include-tree","exclude-tree","include-self","exclude-self","end"];function U(i,e,t){const r=e.split(`
2
+ `),s=[];return r.forEach(n=>{const a=n.split(" ");let o=a[0];for(let l=1;l<a.length;l++){const c=a[l];i.measureText(o+" "+c).width<t?o+=" "+c:(s.push(o),o=c)}s.push(o)}),s}function A(i,e,t,r,s=2){const n=document.createElement("canvas"),a=n.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const l=(window.devicePixelRatio||1)*s;n.width=t*l,n.height=r*l,a.scale(l,l),a.font=e.font,a.fillStyle=e.color,a.textBaseline="top",a.globalAlpha=1;const c=U(a,i,t),g=e.lineHeight;c.forEach((p,m)=>{const w=m*g+2;let T=0;e.textAlign==="center"?T=t/2:e.textAlign==="right"&&(T=t),a.textAlign=e.textAlign,a.fillText(p,T,w)});const u=new d.CanvasTexture(n);return u.colorSpace=d.SRGBColorSpace,u.minFilter=d.LinearFilter,u.magFilter=d.LinearFilter,u.needsUpdate=!0,u}function S(i){return typeof i=="number"?i:parseFloat(i)||0}function X(i,e){var o,l,c,g;if(!e){i.set(0,0,0,0);return}const t=e.split("/")[0].trim().split(/\s+/),r=S(t[0]),s=S((o=t[1])!=null?o:t[0]),n=S((l=t[2])!=null?l:t[0]),a=S((g=(c=t[3])!=null?c:t[1])!=null?g:t[0]);i.set(r,s,n,a)}const W=`
3
3
  varying vec2 vUv;
4
+ varying vec4 vScreenPos;
4
5
  void main() {
5
6
  vUv = uv;
6
7
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
8
+ vScreenPos = gl_Position;
7
9
  }
8
- `,Y=`
10
+ `,H=`
9
11
 
10
12
  varying vec2 vUv;
11
13
 
12
14
  uniform vec2 uSize;
13
- uniform vec4 uRadius;
15
+ uniform vec4 uBorderRadius;
14
16
  uniform float uBorderWidth;
15
17
  uniform vec3 uBgColor;
16
18
  uniform vec3 uBorderColor;
@@ -18,65 +20,66 @@
18
20
  uniform float uBgOpacity;
19
21
  uniform float uBorderOpacity;
20
22
 
21
- // SDF box
23
+ #INJECT_DECLARATIONS
24
+
22
25
  float sdRoundedBox(vec2 p, vec2 b, float r) {
23
26
  vec2 q = abs(p) - b + r;
24
- // return length(max(q, 0.0)) - r;
27
+
25
28
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
26
29
  }
27
30
 
28
31
  void main() {
29
- // uVu: (0.0, 0.0) ~ (1.0, 1.0) / center: (0.5, 0.5)
30
- // p: (-1.0, -1.0) ~ (1.0, 1.0) / center: (0.0, 0.0)
31
32
  vec2 p = (vUv - 0.5) * uSize;
32
33
  vec2 halfSize = uSize * 0.5;
34
+
35
+ #INJECT_UV_MODIFIER
33
36
 
34
- // # border-radius
35
- vec2 xRadii = mix(uRadius.xw, uRadius.yz, step(0.0, p.x));
37
+ // color decision pipeline
38
+ vec4 baseColor = vec4(uBgColor, uBgOpacity);
39
+
40
+ #INJECT_BASE_COLOR
41
+
42
+ // sdf shape pipeline
43
+ vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
36
44
  float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
37
45
  float d = sdRoundedBox(p, halfSize, r);
38
46
 
39
- // 1px blur for anti-aliasing
40
47
  float aa = 1.0;
41
-
42
- // x == 0~aa -> 1
43
- // x < 0 -> 0
44
- // x > aa -> 1
45
48
  float bgMask = 1.0 - smoothstep(0.0, aa, d);
46
49
 
47
- // v valley
48
- // 10px -> 5
49
50
  float halfBorder = uBorderWidth * 0.5;
50
- // 10px :
51
- // 0 -> 0
52
- // -10 -> 0
53
- // -5 -> -5
54
51
  float borderD = abs(d + halfBorder) - halfBorder;
55
- // ^ border 내부는 음수/외부는 양수
56
-
57
- // [!] 내부가 음수인 곳에서 시작점이 0이면 내부는 안티 얼리어싱 되지 않음.
58
- // 1에서 빼든 그대로든 상관없이 경계선 밖 1px
59
-
60
- // 최종 alpha를 위해 border 부분에 해당하는 픽셀 만 1로 반환.
61
52
  float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
62
-
63
53
  if (uBorderWidth <= 0.01) {
64
54
  borderAlpha = 0.0;
65
55
  }
66
56
 
57
+ // final blending (border + background)
67
58
  float aFront = borderAlpha;
68
- float aBack = uBgOpacity;
59
+ float aBack = baseColor.a;
69
60
  float aOut = aFront + aBack * (1.0 - aFront);
70
61
 
71
62
  float safeAlpha = max(aOut, 0.0001);
72
- vec3 cOut = (uBorderColor * aFront + uBgColor * aBack * (1.0 - aFront)) / safeAlpha;
63
+ vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
64
+ vec4 finalColor = vec4(cOut, aOut);
73
65
 
74
- float finalOpacity = aOut * bgMask * uOpacity;
66
+ // final color control (Tint, Noise)
67
+ #INJECT_COLOR_MODIFIER
75
68
 
69
+ float finalOpacity = finalColor.a * bgMask * uOpacity;
76
70
  if (finalOpacity < 0.001) discard;
77
71
 
78
- gl_FragColor = vec4(cOut, finalOpacity);
72
+ gl_FragColor = vec4(finalColor.rgb, finalOpacity);
79
73
 
80
74
  #include <colorspace_fragment>
81
75
  }
82
- `;function S(r){if(!r||r==="transparent")return{color:new d.Color(16777215),alpha:0};const e=r.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(e){const t=parseInt(e[1],10),i=parseInt(e[2],10),s=parseInt(e[3],10),n=e[4]!==void 0?parseFloat(e[4]):1;return{color:new d.Color(`rgb(${t}, ${i}, ${s})`),alpha:n}}return{color:new d.Color(r),alpha:1}}function U(r,e,t){var o;const i=S(r.backgroundColor),s=S(r.borderColor),n={uSize:{value:new d.Vector2(e,t)},uRadius:{value:new d.Vector4(0,0,0,0)},uBorderWidth:{value:T(r.borderWidth)},uBgColor:{value:i.color},uBorderColor:{value:s.color},uOpacity:{value:(o=r.opacity)!=null?o:1},uBgOpacity:{value:i.alpha},uBorderOpacity:{value:s.alpha}};return k(n.uRadius.value,r.borderRadius),new d.ShaderMaterial({uniforms:n,vertexShader:W,fragmentShader:Y,transparent:!0,side:d.FrontSide})}function $(r,e,t,i){var a;const s=S(e.backgroundColor),n=S(e.borderColor);r.uniforms.uSize.value.set(t,i),k(r.uniforms.uRadius.value,e.borderRadius),r.uniforms.uBorderWidth.value=T(e.borderWidth),r.uniforms.uBgColor.value.copy(s.color),r.uniforms.uBorderColor.value.copy(n.color),r.uniforms.uOpacity.value=(a=e.opacity)!=null?a:1,r.uniforms.uBgOpacity.value=s.alpha,r.uniforms.uBorderOpacity.value=n.alpha}const F={create(r,e,t,i,s,n=2){if(r==="BOX")return U(e,i,s);if(r==="TEXT"){const a=X(t||"",e,i,s,n);return new d.MeshBasicMaterial({map:a,transparent:!0,side:d.FrontSide,color:16777215})}return new d.MeshBasicMaterial({visible:!1})},update(r,e,t,i,s,n,a=2){if(e==="BOX")$(r,t,s,n);else if(e==="TEXT"){const o=r;o.map&&o.map.dispose();const h=X(i||"",t,s,n,a);o.map=h,o.needsUpdate=!0}}};class j{constructor(e,t,i){c(this,"canvas");c(this,"scene");c(this,"camera");c(this,"renderer");c(this,"renderOrder",0);c(this,"textQualityFactor",2);c(this,"mode","overlay");c(this,"target");c(this,"mountContainer");c(this,"targetRect");c(this,"meshMap",new Map);var a,o;this.target=e,this.mountContainer=i,this.mode=(a=t.mode)!=null?a:"overlay",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((o=t.textQuality)!=null?o:"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.prepend(this.canvas),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.layers.set(0),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[a,o]of this.meshMap.entries())n.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof d.Material&&o.material.dispose(),this.meshMap.delete(a))}reconcileNode(e,t){t.add(e.element);let i=this.meshMap.get(e.element);if(!i){const s=new d.PlaneGeometry(1,1),n=F.create("BOX",e.styles,"",e.rect.width,e.rect.height);i=new d.Mesh(s,n),i.layers.set(e.visibility),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 o,h;let i=e.children.find(l=>l.name==="TEXT_CHILD");const s=JSON.stringify(t.textStyles),n=(o=i==null?void 0:i.userData)==null?void 0:o.styleHash;if(!i||t.dirtyMask&L||s!==n){i&&((h=i.material.map)==null||h.dispose(),i.geometry.dispose(),e.remove(i));const l=F.create("TEXT",t.textStyles,t.textContent||"",t.rect.width,t.rect.height,this.textQualityFactor),f=new d.PlaneGeometry(1,1);i=new d.Mesh(f,l),i.name="TEXT_CHILD",i.userData={styleHash:s},i.layers.set(t.visibility),i.position.z=.005,e.add(i)}if(i){const l=e.userData.domRect,f=l.x+l.width/2,m=l.y+l.height/2,b=t.rect.x+t.rect.width/2,R=t.rect.y+t.rect.height/2,C=b-f,v=-(R-m);i.position.set(C,v,.005)}}updateMeshProperties(e,t){const{rect:i,styles:s}=t,n=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/n,o=this.renderer.domElement.height/n;e.scale.set(i.width,i.height,1);const h=.001;this.renderOrder++;const l=this.targetRect.left+window.scrollX,f=this.targetRect.top+window.scrollY,m=i.x-l,b=i.y-f;e.position.set(m-a/2+i.width/2,-b+o/2-i.height/2,s.zIndex+this.renderOrder*h),F.update(e.material,"BOX",t.styles,"",t.rect.width,t.rect.height)}render(){this.renderer.render(this.scene,this.camera)}}function Q(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 G(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 _(r,e=B|M|P|L|O,t,i){if(r.nodeType===Node.TEXT_NODE){const u=r;if(!u.textContent||!u.textContent.trim())return null;const y=u.textContent.replace(/\s+/g," ").trim();if(y.length===0)return null;const x=Q(u);if(x.width===0||x.height===0)return null;const I=u.parentElement,z=I?window.getComputedStyle(I):null;return z?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:u,rect:{x:x.left+window.scrollX,y:x.top+window.scrollY,width:x.width,height:x.height},styles:{backgroundColor:"transparent",opacity:parseFloat(z.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:y,textStyles:G(z),dirtyMask:e,visibility:t,children:[]}:null}const s=r,n=s.dataset.mirageFilter;let a=t,o=t;if(n){const u=new Set(n.split(/\s+/));for(const y of u)if(!A.includes(y))throw new Error(`[MirageEngine] Invalid filter token: '${y}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`);if(u.has("end"))return null;if(u.has("include-tree")&&u.has("exclude-tree"))throw new Error("[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.");if(u.has("include-self")&&u.has("exclude-self"))throw new Error("[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.");u.has("include-tree")?a=D:u.has("exclude-tree")&&(a=N),o=a,u.has("include-self")?o=D:u.has("exclude-self")&&(o=N)}if(i&&i.end&&i.end.length>0&&i.end.some(y=>s.classList.contains(y)))return null;const h=s.getBoundingClientRect(),l=window.getComputedStyle(s);if(h.width===0||h.height===0||l.display==="none")return null;let f=s.getAttribute("data-mid");f||(f=Math.random().toString(36).substring(2,11),s.setAttribute("data-mid",f));const m=parseInt(l.zIndex),b={backgroundColor:l.backgroundColor,opacity:parseFloat(l.opacity),zIndex:isNaN(m)?0:m,borderRadius:l.borderRadius,borderColor:l.borderColor,borderWidth:l.borderWidth};let R,C;const v=[];return Array.from(s.childNodes).forEach(u=>{const y=u.nodeType===Node.TEXT_NODE?o:a,x=_(u,e,y,i);x&&v.push(x)}),{id:f,type:"BOX",element:s,rect:{x:h.left+window.scrollX,y:h.top+window.scrollY,width:h.width,height:h.height},styles:b,textContent:R,textStyles:C,dirtyMask:e,visibility:o,children:v}}class q{constructor(e,t,i){c(this,"target");c(this,"renderer");c(this,"filter");c(this,"observer");c(this,"isDomDirty",!1);c(this,"isRunning",!1);c(this,"pendingMask",E);c(this,"mutationTimer",null);c(this,"cssTimer",null);c(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=B|M,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});c(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});c(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});this.target=e,this.renderer=t,this.filter=i,this.observer=new MutationObserver(s=>{let n=E;for(const a of s)a.type==="childList"?n|=O:a.type==="attributes"&&(a.attributeName==="style"||a.attributeName==="class")&&(n|=B|M);if(n!==E){if(this.pendingMask|=n,n&O){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=_(this.target,this.pendingMask,D,this.filter);e&&this.renderer.syncScene(e),this.pendingMask=E}}class V{constructor(e,t){c(this,"renderer");c(this,"syncer");c(this,"target");var s,n,a;this.target=e;let i;if(t.mode==="duplicate"?i=(n=(s=t.container)!=null?s:this.target.parentElement)!=null?n:void 0:i=(a=this.target.parentElement)!=null?a:void 0,!i)throw new Error("[Mirage] Cannot find a container (parent or option).");this.renderer=new j(this.target,t,i),this.renderer.mount(),this.syncer=new q(this.target,this.renderer,t.filter)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}class Z{constructor(e,t){c(this,"_engine");if(!e)throw new Error("[Mirage] Target element is required.");this._engine=new V(e,t)}start(){this._engine.start()}stop(){this._engine.stop()}destroy(){this._engine.dispose()}}p.Mirage=Z,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})});
76
+ `;function B(i){if(!i||i==="transparent")return{color:new d.Color(16777215),alpha:0};const e=i.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(e){const t=parseInt(e[1],10),r=parseInt(e[2],10),s=parseInt(e[3],10),n=e[4]!==void 0?parseFloat(e[4]):1;return{color:new d.Color(`rgb(${t}, ${r}, ${s})`),alpha:n}}return{color:new d.Color(i),alpha:1}}function $(i,e,t,r=null,s){var T;const n=r!==null,a=n?`
77
+ uniform sampler2D uTexture;
78
+ varying vec4 vScreenPos;
79
+ `:"",o=n?`
80
+ vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
81
+ vec2 resultUv = screenUv;
82
+ ${(s==null?void 0:s.uvModifier)||""}
83
+ `:"",l=n?`
84
+ baseColor = texture2D(uTexture, resultUv);
85
+ `:"",c=(s==null?void 0:s.colorModifier)||"",g=H.replace("#INJECT_DECLARATIONS",a).replace("#INJECT_UV_MODIFIER",o).replace("#INJECT_BASE_COLOR",l).replace("#INJECT_COLOR_MODIFIER",c),u=B(i.backgroundColor),p=B(i.borderColor),m={uSize:{value:new d.Vector2(e,t)},uBorderRadius:{value:new d.Vector4(0,0,0,0)},uBorderWidth:{value:S(i.borderWidth)},uBgColor:{value:u.color},uBorderColor:{value:p.color},uOpacity:{value:(T=i.opacity)!=null?T:1},uBgOpacity:{value:u.alpha},uBorderOpacity:{value:p.alpha},uTexture:{value:null}};return X(m.uBorderRadius.value,i.borderRadius),n&&(m.uTexture.value=r),new d.ShaderMaterial({uniforms:m,vertexShader:W,fragmentShader:g,transparent:!0,side:d.FrontSide})}function G(i,e,t,r,s){var o;const n=B(e.backgroundColor),a=B(e.borderColor);i.uniforms.uSize.value.set(t,r),X(i.uniforms.uBorderRadius.value,e.borderRadius),i.uniforms.uBorderWidth.value=S(e.borderWidth),i.uniforms.uBgColor.value.copy(n.color),i.uniforms.uBorderColor.value.copy(a.color),i.uniforms.uOpacity.value=(o=e.opacity)!=null?o:1,i.uniforms.uBgOpacity.value=n.alpha,i.uniforms.uBorderOpacity.value=a.alpha,i.uniforms.uTexture&&s!==void 0&&(i.uniforms.uTexture.value=s)}const L={create(i,e,t,r,s,n=2,a=null){if(i==="BOX")return $(e,r,s,a);if(i==="TEXT"){const o=A(t||"",e,r,s,n);return new d.MeshBasicMaterial({map:o,transparent:!0,side:d.FrontSide,color:16777215})}return new d.MeshBasicMaterial({visible:!1})},update(i,e,t,r,s,n,a=2,o){if(e==="BOX")G(i,t,s,n,o);else if(e==="TEXT"){const l=i;l.map&&l.map.dispose();const c=A(r||"",t,s,n,a);l.map=c,l.needsUpdate=!0}}};class j{constructor(e,t,r){h(this,"canvas");h(this,"scene");h(this,"camera");h(this,"renderer");h(this,"renderTarget",null);h(this,"renderOrder",0);h(this,"qualityFactor",2);h(this,"mode","overlay");h(this,"target");h(this,"mountContainer");h(this,"targetRect");h(this,"meshMap",new Map);var a,o;this.target=e,this.mountContainer=r,this.mode=(a=t.mode)!=null?a:"overlay",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.camera.layers.set(0),this.renderer=new d.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(s,n),this.applyTextQuality((o=t.quality)!=null?o:"medium")}createRenderTarget(){this.renderTarget=new d.WebGLRenderTarget(this.targetRect.width*this.qualityFactor,this.targetRect.height*this.qualityFactor,{minFilter:d.LinearFilter,magFilter:d.LinearFilter,format:d.RGBAFormat,stencilBuffer:!1,depthBuffer:!0,samples:4})}applyTextQuality(e){if(typeof e=="number"){this.qualityFactor=Math.max(.1,e);return}switch(e){case"low":this.qualityFactor=1;break;case"high":this.qualityFactor=4;break;case"medium":default:this.qualityFactor=2;break}}mount(){this.mountContainer.prepend(this.canvas),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.renderTarget&&this.renderTarget.setSize(e*this.qualityFactor,t*this.qualityFactor),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(),r=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);r?(this.targetRect=t,this.setSize(this.targetRect.width,this.targetRect.height),this.updateCanvasLayout()):s?(this.targetRect=t,this.updateCanvasLayout()):this.targetRect=t,this.renderOrder=0;const n=new Set;this.reconcileNode(e,n);for(const[a,o]of this.meshMap.entries())n.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof d.Material&&o.material.dispose(),this.meshMap.delete(a))}reconcileNode(e,t){var s;t.add(e.element);let r=this.meshMap.get(e.element);if(!r){const n=new d.PlaneGeometry(1,1);let a;a=L.create("BOX",e.styles,"",e.rect.width,e.rect.height,this.qualityFactor,e.isTraveler?(s=this.renderTarget)==null?void 0:s.texture:void 0),r=new d.Mesh(n,a),e.type==="TEXT"&&(r.name="BG_MESH"),this.scene.add(r),this.meshMap.set(e.element,r)}if(r.userData.domRect=e.rect,this.updateMeshProperties(r,e),this.updateMeshLayers(r,e),e.isTraveler&&r.layers.enable(28),e.type==="BOX")for(const n of e.children)this.reconcileNode(n,t);else e.type==="TEXT"&&this.reconcileTextChild(r,e)}reconcileTextChild(e,t){var o,l;let r=e.children.find(c=>c.name==="TEXT_CHILD");const s=JSON.stringify(t.textStyles),n=(o=r==null?void 0:r.userData)==null?void 0:o.styleHash;if(!r||t.dirtyMask&N||s!==n){r&&((l=r.material.map)==null||l.dispose(),r.geometry.dispose(),e.remove(r));const c=L.create("TEXT",t.textStyles,t.textContent||"",t.rect.width,t.rect.height,this.qualityFactor),g=new d.PlaneGeometry(1,1);r=new d.Mesh(g,c),r.name="TEXT_CHILD",r.userData={styleHash:s},this.updateMeshLayers(r,t),r.position.z=.005,e.add(r)}if(r){const c=e.userData.domRect,g=c.x+c.width/2,u=c.y+c.height/2,p=t.rect.x+t.rect.width/2,m=t.rect.y+t.rect.height/2,w=p-g,T=-(m-u);r.position.set(w,T,.005)}}updateMeshProperties(e,t){var m;const{rect:r,styles:s}=t,n=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/n,o=this.renderer.domElement.height/n;e.scale.set(r.width,r.height,1);const l=.001;this.renderOrder++;const c=this.targetRect.left+window.scrollX,g=this.targetRect.top+window.scrollY,u=r.x-c,p=r.y-g;e.position.set(u-a/2+r.width/2,-p+o/2-r.height/2,s.zIndex+this.renderOrder*l),L.update(e.material,"BOX",t.styles,"",t.rect.width,t.rect.height,this.qualityFactor,t.isTraveler?(m=this.renderTarget)==null?void 0:m.texture:void 0)}updateMeshLayers(e,t){const r=(1-(t.visibility&C))*30;e.layers.set(r),t.visibility===(C|E)&&e.layers.enable(29)}captureRenderTarget(){const e=[];for(const l of this.meshMap.values())l.layers.mask&1<<28&&e.push(l);if(e.length===0)return;const t=new d.Color,r=this.renderer.getClearAlpha();this.renderer.getClearColor(t),this.renderer.setClearColor(0,0),this.renderer.setRenderTarget(this.renderTarget),this.renderer.clear(),this.renderer.autoClear=!1,this.renderer.setScissorTest(!0),this.camera.layers.set(29);const s=new d.Vector3,n=this.targetRect.width,a=this.targetRect.height,o=this.renderer.getPixelRatio();for(const l of e){s.setFromMatrixPosition(l.matrixWorld),s.project(this.camera);const c=(s.x+1)/2*n,g=(s.y+1)/2*a,u=l.scale.x,p=l.scale.y,m=c-u/2,w=g-p/2,T=m*this.qualityFactor/o,_=w*this.qualityFactor/o,O=u*this.qualityFactor/o,f=p*this.qualityFactor/o;this.renderer.setScissor(T,_,O,f),this.renderer.render(this.scene,this.camera)}this.renderer.setScissorTest(!1),this.renderer.autoClear=!0,this.renderer.setRenderTarget(null),this.camera.layers.set(28),this.renderer.setClearColor(t,r)}render(){this.renderTarget&&this.captureRenderTarget(),this.renderer.render(this.scene,this.camera)}showScissoredRenderTarget(){if(!this.renderTarget)return;const e=this.targetRect.width,t=this.targetRect.height,r=new d.PlaneGeometry(e,t),s=new d.MeshBasicMaterial({map:this.renderTarget.texture,side:d.DoubleSide,transparent:!0,opacity:.8}),n=new d.Mesh(r,s);n.position.set(0,0,90),n.layers.set(28),this.scene.add(n)}}function J(i){const e=document.createRange();e.selectNodeContents(i);const t=e.getBoundingClientRect();return{left:t.left,top:t.top,width:t.width,height:t.height}}function V(i){const e=parseFloat(i.fontSize);let t=parseFloat(i.lineHeight);isNaN(t)&&(t=e*1.2);let r=parseFloat(i.letterSpacing);return isNaN(r)&&(r=0),{font:`${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,color:i.color,textAlign:i.textAlign||"start",textBaseline:"alphabetic",direction:i.direction||"inherit",lineHeight:t,letterSpacing:r}}function P(i,e=F|z|k|N|D,t,r){if(i.nodeType===Node.TEXT_NODE){const f=i;if(!f.textContent||!f.textContent.trim())return null;const R=f.textContent.replace(/\s+/g," ").trim();if(R.length===0)return null;const x=J(f);if(x.width===0||x.height===0)return null;const q=f.parentElement,I=q?window.getComputedStyle(q):null;return I?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:f,rect:{x:x.left+window.scrollX,y:x.top+window.scrollY,width:x.width,height:x.height},styles:{backgroundColor:"transparent",opacity:parseFloat(I.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:R,textStyles:V(I),dirtyMask:e,visibility:t,isTraveler:!1,children:[]}:null}const s=i,n=s.dataset.mirageFilter;let a=t,o=t;if(n){const f=new Set(n.split(/\s+/));for(const R of f)if(!Y.includes(R))throw new Error(`[MirageEngine] Invalid filter token: '${R}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`);if(f.has("end"))return null;if(f.has("include-tree")&&f.has("exclude-tree"))throw new Error("[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.");if(f.has("include-self")&&f.has("exclude-self"))throw new Error("[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.");f.has("include-tree")?a=a|C:f.has("exclude-tree")&&(a=a&~C),o=a,f.has("include-self")?o=o|C:f.has("exclude-self")&&(o=o&~C)}o=o|t&E;const l=s.dataset.mirageTravel;let c=!1;l&&new Set(l.split(/\s+/)).has("traveler")&&(o=o&~E,a=a&~E,c=!0);const g=s.getBoundingClientRect(),u=window.getComputedStyle(s);if(g.width===0||g.height===0||u.display==="none")return null;let p=s.getAttribute("data-mid");p||(p=Math.random().toString(36).substring(2,11),s.setAttribute("data-mid",p));const m=parseInt(u.zIndex),w={backgroundColor:u.backgroundColor,opacity:parseFloat(u.opacity),zIndex:isNaN(m)?0:m,borderRadius:u.borderRadius,borderColor:u.borderColor,borderWidth:u.borderWidth};let T,_;const O=[];return Array.from(s.childNodes).forEach(f=>{const R=f.nodeType===Node.TEXT_NODE?o:a,x=P(f,e,R);x&&O.push(x)}),{id:p,type:"BOX",element:s,rect:{x:g.left+window.scrollX,y:g.top+window.scrollY,width:g.width,height:g.height},styles:w,textContent:T,textStyles:_,dirtyMask:e,visibility:o,isTraveler:c,children:O}}class Q{constructor(e,t,r){h(this,"target");h(this,"renderer");h(this,"filter");h(this,"observer");h(this,"isDomDirty",!1);h(this,"isRunning",!1);h(this,"isTravelEnabled",!1);h(this,"pendingMask",M);h(this,"mutationTimer",null);h(this,"cssTimer",null);h(this,"resizeConfig");h(this,"resizeTimer",null);h(this,"isResizing",!1);h(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=F|z,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});h(this,"onWindowResize",()=>{if(!this.resizeConfig.enabled){this.isDomDirty=!0;return}this.isResizing||(this.isResizing=!0,this.resizeConfig.onStart&&this.resizeConfig.onStart()),this.resizeTimer&&clearTimeout(this.resizeTimer),this.resizeTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.resizeConfig.onEnd&&this.resizeConfig.onEnd(),this.isResizing=!1,this.resizeTimer=null},this.resizeConfig.delay)});h(this,"renderLoop",()=>{this.isRunning&&(this.isDomDirty&&this.forceUpdateScene(),this.renderer.render(),requestAnimationFrame(this.renderLoop))});var n,a;this.target=e,this.renderer=t,this.filter=r.filter;const s=(n=r.resizeDebounce)!=null?n:!0;s===!1?this.resizeConfig={enabled:!1,delay:0}:s===!0?this.resizeConfig={enabled:!0,delay:150}:this.resizeConfig={enabled:!0,delay:(a=s.delay)!=null?a:150,onStart:s.onStart,onEnd:s.onEnd},this.observer=new MutationObserver(o=>{let l=M;for(const c of o)c.type==="childList"?l|=D:c.type==="attributes"&&(c.attributeName==="style"||c.attributeName==="class")&&(l|=F|z);if(l!==M){if(this.pendingMask|=l,l&D){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=document.querySelector("[data-mirage-travel='traveler']")!==null;e&&!this.isTravelEnabled&&(this.isTravelEnabled=!0,this.renderer.createRenderTarget());const t=P(this.target,this.pendingMask,e?C|E:C,this.filter);t&&this.renderer.syncScene(t),this.pendingMask=M}}class Z{constructor(e,t){h(this,"renderer");h(this,"syncer");h(this,"target");var s,n,a;this.target=e;let r;if(t.mode==="duplicate"?r=(n=(s=t.container)!=null?s:this.target.parentElement)!=null?n:void 0:r=(a=this.target.parentElement)!=null?a:void 0,!r)throw new Error("[Mirage] Cannot find a container (parent or option).");this.renderer=new j(this.target,t,r),this.renderer.mount(),this.syncer=new Q(this.target,this.renderer,t)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}class K{constructor(e,t){h(this,"_engine");if(!e)throw new Error("[Mirage] Target element is required.");this._engine=new Z(e,t)}start(){this._engine.start()}stop(){this._engine.stop()}destroy(){this._engine.dispose()}}v.Mirage=K,Object.defineProperty(v,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirage-engine",
3
- "version": "0.2.15",
3
+ "version": "0.2.16",
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",
@@ -21,8 +21,8 @@
21
21
  "dist"
22
22
  ],
23
23
  "dependencies": {
24
- "@mirage-engine/painter": "0.2.3",
25
- "@mirage-engine/core": "0.1.0"
24
+ "@mirage-engine/painter": "0.3.0",
25
+ "@mirage-engine/core": "0.2.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "three": "^0.150.0"