mirage-engine 0.2.15 → 0.2.17

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
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);
2
+ var q = (i, e, t) => e in i ? k(i, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : i[e] = t;
3
+ var c = (i, e, t) => (q(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 S = 0, z = 1, L = 2, Y = 4, I = 8, N = 16, x = 1, R = 2, W = [
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 U(i, e, t) {
13
+ const r = e.split(`
14
14
  `), s = [];
15
- return i.forEach((n) => {
16
- const a = n.split(" ");
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);
15
+ return r.forEach((a) => {
16
+ const n = a.split(" ");
17
+ let o = n[0];
18
+ for (let l = 1; l < n.length; l++) {
19
+ const h = n[l];
20
+ i.measureText(o + " " + h).width < t ? o += " " + h : (s.push(o), o = h);
21
21
  }
22
22
  s.push(o);
23
23
  }), s;
24
24
  }
25
- function L(r, e, t, i, s = 2) {
26
- const n = document.createElement("canvas"), a = n.getContext("2d");
27
- if (!a)
25
+ function A(i, e, t, r, s = 2) {
26
+ const a = document.createElement("canvas"), n = a.getContext("2d");
27
+ if (!n)
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
+ a.width = t * l, a.height = r * l, n.scale(l, l), n.font = e.font, n.fillStyle = e.color, n.textBaseline = "top", n.globalAlpha = 1;
31
+ const h = U(n, i, t), m = e.lineHeight;
32
+ h.forEach((g, p) => {
33
+ const y = p * m + 2;
34
+ let v = 0;
35
+ e.textAlign === "center" ? v = t / 2 : e.textAlign === "right" && (v = t), n.textAlign = e.textAlign, n.fillText(g, v, y);
36
36
  });
37
- const f = new d.CanvasTexture(n);
37
+ const f = new d.CanvasTexture(a);
38
38
  return f.colorSpace = d.SRGBColorSpace, f.minFilter = d.LinearFilter, f.magFilter = d.LinearFilter, f.needsUpdate = !0, f;
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 X(i, e) {
44
+ var o, l, h, m;
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]), a = w((l = t[2]) != null ? l : t[0]), n = w((m = (h = t[3]) != null ? h : t[1]) != null ? m : t[0]);
50
+ i.set(r, s, a, n);
51
51
  }
52
- const W = (
52
+ const H = (
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
+ ), $ = (
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 E(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), a = e[4] !== void 0 ? parseFloat(e[4]) : 1;
141
+ return { color: new d.Color(`rgb(${t}, ${r}, ${s})`), alpha: a };
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 G(i, e, t, r = null, s) {
146
+ var v;
147
+ const a = r !== null, n = a ? (
148
+ /* glsl */
149
+ `
150
+ uniform sampler2D uTexture;
151
+ varying vec4 vScreenPos;
152
+ `
153
+ ) : "", o = a ? (
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 = a ? (
161
+ /* glsl */
162
+ `
163
+ baseColor = texture2D(uTexture, resultUv);
164
+ `
165
+ ) : "", h = (s == null ? void 0 : s.colorModifier) || "", m = $.replace("#INJECT_DECLARATIONS", n).replace("#INJECT_UV_MODIFIER", o).replace("#INJECT_BASE_COLOR", l).replace("#INJECT_COLOR_MODIFIER", h), f = E(i.backgroundColor), g = E(i.borderColor), p = {
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: f.color },
170
+ uBorderColor: { value: g.color },
171
+ uOpacity: { value: (v = i.opacity) != null ? v : 1 },
172
+ uBgOpacity: { value: f.alpha },
173
+ uBorderOpacity: { value: g.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 X(p.uBorderRadius.value, i.borderRadius), a && (p.uTexture.value = r), new d.ShaderMaterial({
177
+ uniforms: p,
178
+ vertexShader: H,
179
+ fragmentShader: m,
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 J(i, e, t, r, s) {
186
+ var o;
187
+ const a = E(e.backgroundColor), n = E(e.borderColor);
188
+ i.uniforms.uSize.value.set(t, r), X(i.uniforms.uBorderRadius.value, e.borderRadius), i.uniforms.uBorderWidth.value = w(e.borderWidth), i.uniforms.uBgColor.value.copy(a.color), i.uniforms.uBorderColor.value.copy(n.color), i.uniforms.uOpacity.value = (o = e.opacity) != null ? o : 1, i.uniforms.uBgOpacity.value = a.alpha, i.uniforms.uBorderOpacity.value = n.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 D = {
191
+ create(i, e, t, r, s, a = 2, n = null, o) {
192
+ if (i === "BOX")
193
+ return G(e, r, s, n, o);
194
+ if (i === "TEXT") {
195
+ const l = A(
183
196
  t || "",
184
197
  e,
185
- i,
198
+ r,
186
199
  s,
187
- n
200
+ a
188
201
  );
189
202
  return new d.MeshBasicMaterial({
190
- map: a,
203
+ map: l,
191
204
  transparent: !0,
192
205
  side: d.FrontSide,
193
206
  color: 16777215
@@ -195,74 +208,91 @@ 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, a, n = 2, o) {
199
212
  if (e === "BOX")
200
- U(
201
- r,
213
+ J(
214
+ i,
202
215
  t,
203
216
  s,
204
- n
217
+ a,
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 h = A(
224
+ r || "",
211
225
  t,
212
226
  s,
213
- n,
214
- a
227
+ a,
228
+ n
215
229
  );
216
- o.map = h, o.needsUpdate = !0;
230
+ l.map = h, l.needsUpdate = !0;
217
231
  }
218
232
  }
219
233
  };
220
- class $ {
221
- constructor(e, t, i) {
234
+ class V {
235
+ constructor(e, t, r) {
222
236
  c(this, "canvas");
223
237
  c(this, "scene");
224
238
  c(this, "camera");
225
239
  c(this, "renderer");
240
+ c(this, "renderTarget", null);
226
241
  c(this, "renderOrder", 0);
227
- c(this, "textQualityFactor", 2);
242
+ c(this, "qualityFactor", 2);
228
243
  c(this, "mode", "overlay");
244
+ c(this, "clipArea", 1);
229
245
  c(this, "target");
230
246
  c(this, "mountContainer");
231
247
  c(this, "targetRect");
232
248
  c(this, "meshMap", /* @__PURE__ */ new Map());
233
- 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();
235
- const s = this.targetRect.width, n = this.targetRect.height;
249
+ var n, o, l;
250
+ this.target = e, this.mountContainer = r, this.mode = (n = t.mode) != null ? n : "overlay", this.clipArea = (o = t.travelerClipArea) != null ? o : 1, this.canvas = document.createElement("canvas"), this.scene = new d.Scene(), this.targetRect = this.target.getBoundingClientRect();
251
+ const s = this.targetRect.width, a = this.targetRect.height;
236
252
  this.camera = new d.OrthographicCamera(
237
253
  s / -2,
238
254
  s / 2,
239
- n / 2,
240
- n / -2,
255
+ a / 2,
256
+ a / -2,
241
257
  1,
242
258
  1e3
243
- ), this.camera.position.z = 100, this.renderer = new d.WebGLRenderer({
259
+ ), this.camera.position.z = 100, this.camera.layers.set(0), this.renderer = new d.WebGLRenderer({
244
260
  canvas: this.canvas,
245
261
  alpha: !0,
246
262
  antialias: !0
247
263
  // [new]
248
264
  // premultipliedAlpha: true
249
- }), this.renderer.setPixelRatio(window.devicePixelRatio), this.renderer.setSize(s, n), this.applyTextQuality((o = t.textQuality) != null ? o : "medium");
265
+ }), this.renderer.setPixelRatio(window.devicePixelRatio), this.renderer.setSize(s, a), this.applyTextQuality((l = t.quality) != null ? l : "medium");
266
+ }
267
+ createRenderTarget() {
268
+ this.renderTarget = new d.WebGLRenderTarget(
269
+ this.targetRect.width * this.qualityFactor,
270
+ this.targetRect.height * this.qualityFactor,
271
+ {
272
+ minFilter: d.LinearFilter,
273
+ magFilter: d.LinearFilter,
274
+ format: d.RGBAFormat,
275
+ stencilBuffer: !1,
276
+ depthBuffer: !0,
277
+ samples: 4
278
+ }
279
+ );
250
280
  }
251
281
  applyTextQuality(e) {
252
282
  if (typeof e == "number") {
253
- this.textQualityFactor = Math.max(0.1, e);
283
+ this.qualityFactor = Math.max(0.1, e);
254
284
  return;
255
285
  }
256
286
  switch (e) {
257
287
  case "low":
258
- this.textQualityFactor = 1;
288
+ this.qualityFactor = 1;
259
289
  break;
260
290
  case "high":
261
- this.textQualityFactor = 4;
291
+ this.qualityFactor = 4;
262
292
  break;
263
293
  case "medium":
264
294
  default:
265
- this.textQualityFactor = 2;
295
+ this.qualityFactor = 2;
266
296
  break;
267
297
  }
268
298
  }
@@ -276,84 +306,130 @@ class $ {
276
306
  this.renderer.dispose(), this.canvas.remove();
277
307
  }
278
308
  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();
309
+ this.renderer.setSize(e, t), this.renderTarget && this.renderTarget.setSize(
310
+ e * this.qualityFactor,
311
+ t * this.qualityFactor
312
+ ), this.camera.left = e / -2, this.camera.right = e / 2, this.camera.top = t / 2, this.camera.bottom = t / -2, this.camera.updateProjectionMatrix();
280
313
  }
281
314
  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;
284
- const n = /* @__PURE__ */ new Set();
285
- this.reconcileNode(e, n);
286
- for (const [a, o] of this.meshMap.entries())
287
- n.has(a) || (this.scene.remove(o), o.geometry.dispose(), o.material instanceof d.Material && o.material.dispose(), this.meshMap.delete(a));
315
+ 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);
316
+ 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;
317
+ const a = /* @__PURE__ */ new Set();
318
+ this.reconcileNode(e, a);
319
+ for (const [n, o] of this.meshMap.entries())
320
+ a.has(n) || (this.scene.remove(o), o.geometry.dispose(), o.material instanceof d.Material && o.material.dispose(), this.meshMap.delete(n));
288
321
  }
289
322
  reconcileNode(e, t) {
323
+ var s;
290
324
  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(
325
+ let r = this.meshMap.get(e.element);
326
+ if (!r) {
327
+ const a = new d.PlaneGeometry(1, 1);
328
+ let n;
329
+ n = D.create(
294
330
  "BOX",
295
331
  e.styles,
296
332
  "",
297
333
  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);
334
+ e.rect.height,
335
+ this.qualityFactor,
336
+ e.isTraveler ? (s = this.renderTarget) == null ? void 0 : s.texture : void 0,
337
+ e.shaderHooks
338
+ ), r = new d.Mesh(a, n), e.type === "TEXT" && (r.name = "BG_MESH"), this.scene.add(r), this.meshMap.set(e.element, r);
301
339
  }
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);
340
+ if (r.userData.domRect = e.rect, this.updateMeshProperties(r, e), this.updateMeshLayers(r, e), e.isTraveler && r.layers.enable(28), e.type === "BOX")
341
+ for (const a of e.children)
342
+ this.reconcileNode(a, t);
305
343
  else
306
- e.type === "TEXT" && this.reconcileTextChild(i, e);
344
+ e.type === "TEXT" && this.reconcileTextChild(r, e);
307
345
  }
308
346
  reconcileTextChild(e, t) {
309
- var o, h;
310
- let i = e.children.find(
311
- (l) => l.name === "TEXT_CHILD"
347
+ var o, l;
348
+ let r = e.children.find(
349
+ (h) => h.name === "TEXT_CHILD"
312
350
  );
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(
351
+ const s = JSON.stringify(t.textStyles), a = (o = r == null ? void 0 : r.userData) == null ? void 0 : o.styleHash;
352
+ if (!r || t.dirtyMask & N || s !== a) {
353
+ r && ((l = r.material.map) == null || l.dispose(), r.geometry.dispose(), e.remove(r));
354
+ const h = D.create(
317
355
  "TEXT",
318
356
  t.textStyles,
319
357
  t.textContent || "",
320
358
  t.rect.width,
321
359
  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);
360
+ this.qualityFactor
361
+ ), m = new d.PlaneGeometry(1, 1);
362
+ r = new d.Mesh(m, h), r.name = "TEXT_CHILD", r.userData = { styleHash: s }, this.updateMeshLayers(r, t), r.position.z = 5e-3, e.add(r);
325
363
  }
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);
364
+ if (r) {
365
+ const h = e.userData.domRect, m = h.x + h.width / 2, f = h.y + h.height / 2, g = t.rect.x + t.rect.width / 2, p = t.rect.y + t.rect.height / 2, y = g - m, v = -(p - f);
366
+ r.position.set(y, v, 5e-3);
329
367
  }
330
368
  }
331
369
  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;
370
+ var p;
371
+ const { rect: r, styles: s } = t, a = this.renderer.getPixelRatio(), n = this.renderer.domElement.width / a, o = this.renderer.domElement.height / a;
372
+ e.scale.set(r.width, r.height, 1);
373
+ const l = 1e-3;
335
374
  this.renderOrder++;
336
- const l = this.targetRect.left + window.scrollX, p = this.targetRect.top + window.scrollY, f = i.x - l, x = i.y - p;
375
+ const h = this.targetRect.left + window.scrollX, m = this.targetRect.top + window.scrollY, f = r.x - h, g = r.y - m;
337
376
  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(
377
+ f - n / 2 + r.width / 2,
378
+ -g + o / 2 - r.height / 2,
379
+ s.zIndex + this.renderOrder * l
380
+ ), D.update(
342
381
  e.material,
343
382
  "BOX",
344
383
  t.styles,
345
384
  "",
346
385
  t.rect.width,
347
- t.rect.height
386
+ t.rect.height,
387
+ this.qualityFactor,
388
+ t.isTraveler ? (p = this.renderTarget) == null ? void 0 : p.texture : void 0
348
389
  );
349
390
  }
391
+ updateMeshLayers(e, t) {
392
+ const r = (1 - (t.visibility & x)) * 30;
393
+ e.layers.set(r), t.visibility === (x | R) && e.layers.enable(29);
394
+ }
395
+ captureRenderTarget() {
396
+ const e = [];
397
+ for (const l of this.meshMap.values())
398
+ l.layers.mask & 1 << 28 && e.push(l);
399
+ if (e.length === 0)
400
+ return;
401
+ const t = new d.Color(), r = this.renderer.getClearAlpha();
402
+ 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);
403
+ const s = new d.Vector3(), a = this.targetRect.width, n = this.targetRect.height, o = this.renderer.getPixelRatio();
404
+ for (const l of e) {
405
+ s.setFromMatrixPosition(l.matrixWorld), s.project(this.camera);
406
+ const h = (s.x + 1) / 2 * a, m = (s.y + 1) / 2 * n;
407
+ let f = 0, g = 1;
408
+ typeof this.clipArea == "number" ? g = this.clipArea : this.clipArea.endsWith("%") ? g = parseFloat(this.clipArea) / 100 : this.clipArea.endsWith("px") && (f = parseFloat(this.clipArea));
409
+ const p = l.scale.x * g + 0.5, y = l.scale.y * g + 0.5, v = h - p / 2, B = m - y / 2, M = (v * this.qualityFactor - f) / o, F = (B * this.qualityFactor - f) / o, b = (p * this.qualityFactor + f * 2) / o, u = (y * this.qualityFactor + f * 2) / o;
410
+ this.renderer.setScissor(M, F, b, u), this.renderer.render(this.scene, this.camera);
411
+ }
412
+ this.renderer.setScissorTest(!1), this.renderer.autoClear = !0, this.renderer.setRenderTarget(null), this.camera.layers.set(28), this.renderer.setClearColor(t, r);
413
+ }
350
414
  render() {
351
- this.renderer.render(this.scene, this.camera);
415
+ this.renderTarget && this.captureRenderTarget(), this.renderer.render(this.scene, this.camera);
416
+ }
417
+ // for debugging
418
+ showScissoredRenderTarget() {
419
+ if (!this.renderTarget)
420
+ return;
421
+ const e = this.targetRect.width, t = this.targetRect.height, r = new d.PlaneGeometry(e, t), s = new d.MeshBasicMaterial({
422
+ map: this.renderTarget.texture,
423
+ side: d.DoubleSide,
424
+ transparent: !0,
425
+ opacity: 0.8
426
+ }), a = new d.Mesh(r, s);
427
+ a.position.set(0, 0, 90), a.layers.set(28), this.scene.add(a);
352
428
  }
353
429
  }
354
- function Q(r) {
430
+ function j(i) {
355
431
  const e = document.createRange();
356
- e.selectNodeContents(r);
432
+ e.selectNodeContents(i);
357
433
  const t = e.getBoundingClientRect();
358
434
  return {
359
435
  left: t.left,
@@ -362,66 +438,67 @@ function Q(r) {
362
438
  height: t.height
363
439
  };
364
440
  }
365
- function G(r) {
366
- const e = parseFloat(r.fontSize);
367
- let t = parseFloat(r.lineHeight);
441
+ function Q(i) {
442
+ const e = parseFloat(i.fontSize);
443
+ let t = parseFloat(i.lineHeight);
368
444
  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",
445
+ let r = parseFloat(i.letterSpacing);
446
+ return isNaN(r) && (r = 0), {
447
+ font: `${i.fontStyle} ${i.fontWeight} ${i.fontSize} ${i.fontFamily}`,
448
+ color: i.color,
449
+ textAlign: i.textAlign || "start",
374
450
  textBaseline: "alphabetic",
375
- direction: r.direction || "inherit",
451
+ direction: i.direction || "inherit",
376
452
  lineHeight: t,
377
- letterSpacing: i
453
+ letterSpacing: r
378
454
  };
379
455
  }
380
- function N(r, e = S | B | _ | z | M, t, i) {
381
- if (r.nodeType === Node.TEXT_NODE) {
382
- const u = r;
456
+ function P(i, e = z | L | Y | N | I, t, r) {
457
+ if (i.nodeType === Node.TEXT_NODE) {
458
+ const u = i;
383
459
  if (!u.textContent || !u.textContent.trim())
384
460
  return null;
385
- const m = u.textContent.replace(/\s+/g, " ").trim();
386
- if (m.length === 0)
461
+ const C = u.textContent.replace(/\s+/g, " ").trim();
462
+ if (C.length === 0)
387
463
  return null;
388
- const g = Q(u);
389
- if (g.width === 0 || g.height === 0)
464
+ const T = j(u);
465
+ if (T.width === 0 || T.height === 0)
390
466
  return null;
391
- const O = u.parentElement, C = O ? window.getComputedStyle(O) : null;
392
- return C ? {
467
+ const _ = u.parentElement, O = _ ? window.getComputedStyle(_) : null;
468
+ return O ? {
393
469
  id: Math.random().toString(36).substring(2, 9),
394
470
  type: "TEXT",
395
471
  element: u,
396
472
  rect: {
397
- x: g.left + window.scrollX,
398
- y: g.top + window.scrollY,
399
- width: g.width,
400
- height: g.height
473
+ x: T.left + window.scrollX,
474
+ y: T.top + window.scrollY,
475
+ width: T.width,
476
+ height: T.height
401
477
  },
402
478
  styles: {
403
479
  backgroundColor: "transparent",
404
- opacity: parseFloat(C.opacity),
480
+ opacity: parseFloat(O.opacity),
405
481
  zIndex: 0,
406
482
  borderRadius: "0px",
407
483
  borderColor: "transparent",
408
484
  borderWidth: "0px"
409
485
  },
410
- textContent: m,
411
- textStyles: G(C),
486
+ textContent: C,
487
+ textStyles: Q(O),
412
488
  dirtyMask: e,
413
489
  visibility: t,
490
+ isTraveler: !1,
414
491
  children: []
415
492
  } : null;
416
493
  }
417
- const s = r, n = s.dataset.mirageFilter;
418
- let a = t, o = t;
419
- if (n) {
420
- const u = new Set(n.split(/\s+/));
421
- for (const m of u)
422
- if (!A.includes(m))
494
+ const s = i, a = s.dataset.mirageFilter;
495
+ let n = t, o = t;
496
+ if (a) {
497
+ const u = new Set(a.split(/\s+/));
498
+ for (const C of u)
499
+ if (!W.includes(C))
423
500
  throw new Error(
424
- `[MirageEngine] Invalid filter token: '${m}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`
501
+ `[MirageEngine] Invalid filter token: '${C}'. Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`
425
502
  );
426
503
  if (u.has("end"))
427
504
  return null;
@@ -433,81 +510,103 @@ function N(r, e = S | B | _ | z | M, t, i) {
433
510
  throw new Error(
434
511
  "[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element."
435
512
  );
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")
513
+ u.has("include-tree") ? n = n | x : u.has("exclude-tree") && (n = n & ~x), o = n, u.has("include-self") ? o = o | x : u.has("exclude-self") && (o = o & ~x);
514
+ }
515
+ o = o | t & R;
516
+ const l = s.dataset.mirageTravel;
517
+ let h = !1;
518
+ l && new Set(l.split(/\s+/)).has("traveler") && (o = o & ~R, n = n & ~R, h = !0);
519
+ const m = s.dataset.mirageShader;
520
+ let f;
521
+ m && (f = JSON.parse(m));
522
+ const g = s.getBoundingClientRect(), p = window.getComputedStyle(s);
523
+ if (g.width === 0 || g.height === 0 || p.display === "none")
444
524
  return null;
445
- let p = s.getAttribute("data-mid");
446
- 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
525
+ let y = s.getAttribute("data-mid");
526
+ y || (y = Math.random().toString(36).substring(2, 11), s.setAttribute("data-mid", y));
527
+ const v = parseInt(p.zIndex), B = {
528
+ backgroundColor: p.backgroundColor,
529
+ opacity: parseFloat(p.opacity),
530
+ zIndex: isNaN(v) ? 0 : v,
531
+ borderRadius: p.borderRadius,
532
+ borderColor: p.borderColor,
533
+ borderWidth: p.borderWidth
454
534
  };
455
- let w, b;
456
- const y = [];
535
+ let M, F;
536
+ const b = [];
457
537
  return Array.from(s.childNodes).forEach((u) => {
458
- const m = u.nodeType === Node.TEXT_NODE ? o : a, g = N(
538
+ const C = u.nodeType === Node.TEXT_NODE ? o : n, T = P(
459
539
  u,
460
540
  e,
461
- m,
462
- i
541
+ C
463
542
  );
464
- g && y.push(g);
543
+ T && b.push(T);
465
544
  }), {
466
- id: p,
545
+ id: y,
467
546
  type: "BOX",
468
547
  element: s,
469
548
  rect: {
470
- x: h.left + window.scrollX,
471
- y: h.top + window.scrollY,
472
- width: h.width,
473
- height: h.height
549
+ x: g.left + window.scrollX,
550
+ y: g.top + window.scrollY,
551
+ width: g.width,
552
+ height: g.height
474
553
  },
475
- styles: x,
476
- textContent: w,
477
- textStyles: b,
554
+ styles: B,
555
+ textContent: M,
556
+ textStyles: F,
478
557
  dirtyMask: e,
479
558
  visibility: o,
480
- children: y
559
+ isTraveler: h,
560
+ children: b,
561
+ shaderHooks: f
481
562
  };
482
563
  }
483
- class q {
484
- constructor(e, t, i) {
564
+ class Z {
565
+ constructor(e, t, r) {
485
566
  c(this, "target");
486
567
  c(this, "renderer");
487
568
  c(this, "filter");
488
569
  c(this, "observer");
489
570
  c(this, "isDomDirty", !1);
490
571
  c(this, "isRunning", !1);
491
- c(this, "pendingMask", T);
572
+ c(this, "isTravelEnabled", !1);
573
+ c(this, "pendingMask", S);
492
574
  c(this, "mutationTimer", null);
493
575
  c(this, "cssTimer", null);
576
+ c(this, "resizeConfig");
577
+ c(this, "resizeTimer", null);
578
+ c(this, "isResizing", !1);
494
579
  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(() => {
580
+ this.target.contains(e.target) && this.mutationTimer === null && (this.cssTimer && clearTimeout(this.cssTimer), this.pendingMask |= z | L, this.cssTimer = window.setTimeout(() => {
496
581
  this.isDomDirty = !0, this.cssTimer = null;
497
582
  }, 50));
498
583
  });
499
584
  c(this, "onWindowResize", () => {
500
- this.renderer.setSize(window.innerWidth, window.innerHeight), this.isDomDirty = !0;
585
+ if (!this.resizeConfig.enabled) {
586
+ this.isDomDirty = !0;
587
+ return;
588
+ }
589
+ this.isResizing || (this.isResizing = !0, this.resizeConfig.onStart && this.resizeConfig.onStart()), this.resizeTimer && clearTimeout(this.resizeTimer), this.resizeTimer = window.setTimeout(() => {
590
+ this.isDomDirty = !0, this.resizeConfig.onEnd && this.resizeConfig.onEnd(), this.isResizing = !1, this.resizeTimer = null;
591
+ }, this.resizeConfig.delay);
501
592
  });
502
593
  c(this, "renderLoop", () => {
503
594
  this.isRunning && (this.isDomDirty && this.forceUpdateScene(), this.renderer.render(), requestAnimationFrame(this.renderLoop));
504
595
  });
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) {
596
+ var a, n;
597
+ this.target = e, this.renderer = t, this.filter = r.filter;
598
+ const s = (a = r.resizeDebounce) != null ? a : !0;
599
+ s === !1 ? this.resizeConfig = { enabled: !1, delay: 0 } : s === !0 ? this.resizeConfig = { enabled: !0, delay: 150 } : this.resizeConfig = {
600
+ enabled: !0,
601
+ delay: (n = s.delay) != null ? n : 150,
602
+ onStart: s.onStart,
603
+ onEnd: s.onEnd
604
+ }, this.observer = new MutationObserver((o) => {
605
+ let l = S;
606
+ for (const h of o)
607
+ h.type === "childList" ? l |= I : h.type === "attributes" && (h.attributeName === "style" || h.attributeName === "class") && (l |= z | L);
608
+ if (l !== S) {
609
+ if (this.pendingMask |= l, l & I) {
511
610
  this.clearTimers(), console.log("Structural Change detected"), this.isDomDirty = !0;
512
611
  return;
513
612
  }
@@ -533,21 +632,28 @@ class q {
533
632
  }
534
633
  forceUpdateScene() {
535
634
  this.isDomDirty = !1;
536
- const e = N(this.target, this.pendingMask, D, this.filter);
537
- e && this.renderer.syncScene(e), this.pendingMask = T;
635
+ const e = document.querySelector("[data-mirage-travel='traveler']") !== null;
636
+ e && !this.isTravelEnabled && (this.isTravelEnabled = !0, this.renderer.createRenderTarget());
637
+ const t = P(
638
+ this.target,
639
+ this.pendingMask,
640
+ e ? x | R : x,
641
+ this.filter
642
+ );
643
+ t && this.renderer.syncScene(t), this.pendingMask = S;
538
644
  }
539
645
  }
540
- class V {
646
+ class K {
541
647
  constructor(e, t) {
542
648
  c(this, "renderer");
543
649
  c(this, "syncer");
544
650
  c(this, "target");
545
- var s, n, a;
651
+ var s, a, n;
546
652
  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)
653
+ let r;
654
+ if (t.mode === "duplicate" ? r = (a = (s = t.container) != null ? s : this.target.parentElement) != null ? a : void 0 : r = (n = this.target.parentElement) != null ? n : void 0, !r)
549
655
  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);
656
+ this.renderer = new V(this.target, t, r), this.renderer.mount(), this.syncer = new Z(this.target, this.renderer, t);
551
657
  }
552
658
  start() {
553
659
  this.syncer.start();
@@ -559,12 +665,12 @@ class V {
559
665
  this.syncer.stop(), this.renderer.dispose();
560
666
  }
561
667
  }
562
- class Z {
668
+ class te {
563
669
  constructor(e, t) {
564
670
  c(this, "_engine");
565
671
  if (!e)
566
672
  throw new Error("[Mirage] Target element is required.");
567
- this._engine = new V(e, t);
673
+ this._engine = new K(e, t);
568
674
  }
569
675
  start() {
570
676
  this._engine.start();
@@ -577,5 +683,5 @@ class Z {
577
683
  }
578
684
  }
579
685
  export {
580
- Z as Mirage
686
+ te as Mirage
581
687
  };
@@ -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 re=Object.defineProperty;var ie=(v,y,b)=>y in v?re(v,y,{enumerable:!0,configurable:!0,writable:!0,value:b}):v[y]=b;var c=(v,y,b)=>(ie(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,D=2,W=4,z=8,X=16,w=1,E=2,H=["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 h=a[l];i.measureText(o+" "+h).width<t?o+=" "+h:(s.push(o),o=h)}s.push(o)}),s}function P(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 h=U(a,i,t),m=e.lineHeight;h.forEach((g,p)=>{const x=p*m+2;let T=0;e.textAlign==="center"?T=t/2:e.textAlign==="right"&&(T=t),a.textAlign=e.textAlign,a.fillText(g,T,x)});const f=new d.CanvasTexture(n);return f.colorSpace=d.SRGBColorSpace,f.minFilter=d.LinearFilter,f.magFilter=d.LinearFilter,f.needsUpdate=!0,f}function S(i){return typeof i=="number"?i:parseFloat(i)||0}function k(i,e){var o,l,h,m;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((m=(h=t[3])!=null?h:t[1])!=null?m:t[0]);i.set(r,s,n,a)}const $=`
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
+ `,G=`
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 J(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
+ `:"",h=(s==null?void 0:s.colorModifier)||"",m=G.replace("#INJECT_DECLARATIONS",a).replace("#INJECT_UV_MODIFIER",o).replace("#INJECT_BASE_COLOR",l).replace("#INJECT_COLOR_MODIFIER",h),f=B(i.backgroundColor),g=B(i.borderColor),p={uSize:{value:new d.Vector2(e,t)},uBorderRadius:{value:new d.Vector4(0,0,0,0)},uBorderWidth:{value:S(i.borderWidth)},uBgColor:{value:f.color},uBorderColor:{value:g.color},uOpacity:{value:(T=i.opacity)!=null?T:1},uBgOpacity:{value:f.alpha},uBorderOpacity:{value:g.alpha},uTexture:{value:null}};return k(p.uBorderRadius.value,i.borderRadius),n&&(p.uTexture.value=r),new d.ShaderMaterial({uniforms:p,vertexShader:$,fragmentShader:m,transparent:!0,side:d.FrontSide})}function j(i,e,t,r,s){var o;const n=B(e.backgroundColor),a=B(e.borderColor);i.uniforms.uSize.value.set(t,r),k(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,o){if(i==="BOX")return J(e,r,s,a,o);if(i==="TEXT"){const l=P(t||"",e,r,s,n);return new d.MeshBasicMaterial({map:l,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")j(i,t,s,n,o);else if(e==="TEXT"){const l=i;l.map&&l.map.dispose();const h=P(r||"",t,s,n,a);l.map=h,l.needsUpdate=!0}}};class V{constructor(e,t,r){c(this,"canvas");c(this,"scene");c(this,"camera");c(this,"renderer");c(this,"renderTarget",null);c(this,"renderOrder",0);c(this,"qualityFactor",2);c(this,"mode","overlay");c(this,"clipArea",1);c(this,"target");c(this,"mountContainer");c(this,"targetRect");c(this,"meshMap",new Map);var a,o,l;this.target=e,this.mountContainer=r,this.mode=(a=t.mode)!=null?a:"overlay",this.clipArea=(o=t.travelerClipArea)!=null?o:1,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((l=t.quality)!=null?l:"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,e.shaderHooks),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(h=>h.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&X||s!==n){r&&((l=r.material.map)==null||l.dispose(),r.geometry.dispose(),e.remove(r));const h=L.create("TEXT",t.textStyles,t.textContent||"",t.rect.width,t.rect.height,this.qualityFactor),m=new d.PlaneGeometry(1,1);r=new d.Mesh(m,h),r.name="TEXT_CHILD",r.userData={styleHash:s},this.updateMeshLayers(r,t),r.position.z=.005,e.add(r)}if(r){const h=e.userData.domRect,m=h.x+h.width/2,f=h.y+h.height/2,g=t.rect.x+t.rect.width/2,p=t.rect.y+t.rect.height/2,x=g-m,T=-(p-f);r.position.set(x,T,.005)}}updateMeshProperties(e,t){var p;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 h=this.targetRect.left+window.scrollX,m=this.targetRect.top+window.scrollY,f=r.x-h,g=r.y-m;e.position.set(f-a/2+r.width/2,-g+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?(p=this.renderTarget)==null?void 0:p.texture:void 0)}updateMeshLayers(e,t){const r=(1-(t.visibility&w))*30;e.layers.set(r),t.visibility===(w|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 h=(s.x+1)/2*n,m=(s.y+1)/2*a;let f=0,g=1;typeof this.clipArea=="number"?g=this.clipArea:this.clipArea.endsWith("%")?g=parseFloat(this.clipArea)/100:this.clipArea.endsWith("px")&&(f=parseFloat(this.clipArea));const p=l.scale.x*g+.5,x=l.scale.y*g+.5,T=h-p/2,_=m-x/2,I=(T*this.qualityFactor-f)/o,N=(_*this.qualityFactor-f)/o,O=(p*this.qualityFactor+f*2)/o,u=(x*this.qualityFactor+f*2)/o;this.renderer.setScissor(I,N,O,u),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 Q(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 Z(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 q(i,e=F|D|W|X|z,t,r){if(i.nodeType===Node.TEXT_NODE){const u=i;if(!u.textContent||!u.textContent.trim())return null;const R=u.textContent.replace(/\s+/g," ").trim();if(R.length===0)return null;const C=Q(u);if(C.width===0||C.height===0)return null;const Y=u.parentElement,A=Y?window.getComputedStyle(Y):null;return A?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:u,rect:{x:C.left+window.scrollX,y:C.top+window.scrollY,width:C.width,height:C.height},styles:{backgroundColor:"transparent",opacity:parseFloat(A.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:R,textStyles:Z(A),dirtyMask:e,visibility:t,isTraveler:!1,children:[]}:null}const s=i,n=s.dataset.mirageFilter;let a=t,o=t;if(n){const u=new Set(n.split(/\s+/));for(const R of u)if(!H.includes(R))throw new Error(`[MirageEngine] Invalid filter token: '${R}'. 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=a|w:u.has("exclude-tree")&&(a=a&~w),o=a,u.has("include-self")?o=o|w:u.has("exclude-self")&&(o=o&~w)}o=o|t&E;const l=s.dataset.mirageTravel;let h=!1;l&&new Set(l.split(/\s+/)).has("traveler")&&(o=o&~E,a=a&~E,h=!0);const m=s.dataset.mirageShader;let f;m&&(f=JSON.parse(m));const g=s.getBoundingClientRect(),p=window.getComputedStyle(s);if(g.width===0||g.height===0||p.display==="none")return null;let x=s.getAttribute("data-mid");x||(x=Math.random().toString(36).substring(2,11),s.setAttribute("data-mid",x));const T=parseInt(p.zIndex),_={backgroundColor:p.backgroundColor,opacity:parseFloat(p.opacity),zIndex:isNaN(T)?0:T,borderRadius:p.borderRadius,borderColor:p.borderColor,borderWidth:p.borderWidth};let I,N;const O=[];return Array.from(s.childNodes).forEach(u=>{const R=u.nodeType===Node.TEXT_NODE?o:a,C=q(u,e,R);C&&O.push(C)}),{id:x,type:"BOX",element:s,rect:{x:g.left+window.scrollX,y:g.top+window.scrollY,width:g.width,height:g.height},styles:_,textContent:I,textStyles:N,dirtyMask:e,visibility:o,isTraveler:h,children:O,shaderHooks:f}}class K{constructor(e,t,r){c(this,"target");c(this,"renderer");c(this,"filter");c(this,"observer");c(this,"isDomDirty",!1);c(this,"isRunning",!1);c(this,"isTravelEnabled",!1);c(this,"pendingMask",M);c(this,"mutationTimer",null);c(this,"cssTimer",null);c(this,"resizeConfig");c(this,"resizeTimer",null);c(this,"isResizing",!1);c(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=F|D,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});c(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)});c(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 h of o)h.type==="childList"?l|=z:h.type==="attributes"&&(h.attributeName==="style"||h.attributeName==="class")&&(l|=F|D);if(l!==M){if(this.pendingMask|=l,l&z){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=q(this.target,this.pendingMask,e?w|E:w,this.filter);t&&this.renderer.syncScene(t),this.pendingMask=M}}class ee{constructor(e,t){c(this,"renderer");c(this,"syncer");c(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 V(this.target,t,r),this.renderer.mount(),this.syncer=new K(this.target,this.renderer,t)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}class te{constructor(e,t){c(this,"_engine");if(!e)throw new Error("[Mirage] Target element is required.");this._engine=new ee(e,t)}start(){this._engine.start()}stop(){this._engine.stop()}destroy(){this._engine.dispose()}}v.Mirage=te,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.17",
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.4.0",
25
+ "@mirage-engine/core": "0.2.1"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "three": "^0.150.0"