@mirage-engine/core 0.0.2 → 0.1.0

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,55 +1,82 @@
1
- (function(d,f){typeof exports=="object"&&typeof module!="undefined"?f(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],f):(d=typeof globalThis!="undefined"?globalThis:d||self,f(d.MirageEngine={},d.THREE))})(this,function(d,f){"use strict";var H=Object.defineProperty;var W=(d,f,T)=>f in d?H(d,f,{enumerable:!0,configurable:!0,writable:!0,value:T}):d[f]=T;var c=(d,f,T)=>(W(d,typeof f!="symbol"?f+"":f,T),T);function T(r){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const e in r)if(e!=="default"){const i=Object.getOwnPropertyDescriptor(r,e);Object.defineProperty(t,e,i.get?i:{enumerable:!0,get:()=>r[e]})}}return t.default=r,Object.freeze(t)}const h=T(f),x=0,R=1,b=2,B=4,C=8,M=16;function I(r,t,e){const i=t.split(`
2
- `),n=[];return i.forEach(s=>{const a=s.split(" ");let o=a[0];for(let l=1;l<a.length;l++){const u=a[l];r.measureText(o+" "+u).width<e?o+=" "+u:(n.push(o),o=u)}n.push(o)}),n}function O(r,t,e,i,n=2){const s=document.createElement("canvas"),a=s.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const l=(window.devicePixelRatio||1)*n;s.width=e*l,s.height=i*l,a.scale(l,l),a.font=t.font,a.fillStyle=t.color,a.textBaseline="top",a.globalAlpha=1;const u=I(a,r,e),m=t.lineHeight;u.forEach((g,y)=>{const w=y*m+2;let v=0;t.textAlign==="center"?v=e/2:t.textAlign==="right"&&(v=e),a.textAlign=t.textAlign,a.fillText(g,v,w)});const p=new h.CanvasTexture(s);return p.colorSpace=h.SRGBColorSpace,p.minFilter=h.LinearFilter,p.magFilter=h.LinearFilter,p.needsUpdate=!0,p}function S(r){return typeof r=="number"?r:parseFloat(r)||0}const F=`
1
+ (function(d,m){typeof exports=="object"&&typeof module!="undefined"?m(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],m):(d=typeof globalThis!="undefined"?globalThis:d||self,m(d.MirageEngine={},d.THREE))})(this,function(d,m){"use strict";var J=Object.defineProperty;var K=(d,m,T)=>m in d?J(d,m,{enumerable:!0,configurable:!0,writable:!0,value:T}):d[m]=T;var l=(d,m,T)=>(K(d,typeof m!="symbol"?m+"":m,T),T);function T(r){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const t in r)if(t!=="default"){const i=Object.getOwnPropertyDescriptor(r,t);Object.defineProperty(e,t,i.get?i:{enumerable:!0,get:()=>r[t]})}}return e.default=r,Object.freeze(e)}const u=T(m),R=0,S=1,D=2,I=4,B=8,F=16,M=0,A=29,L=30,X=["include-tree","exclude-tree","include-self","exclude-self","end"];function U(r,e,t){const i=e.split(`
2
+ `),n=[];return i.forEach(s=>{const a=s.split(" ");let o=a[0];for(let h=1;h<a.length;h++){const c=a[h];r.measureText(o+" "+c).width<t?o+=" "+c:(n.push(o),o=c)}n.push(o)}),n}function _(r,e,t,i,n=2){const s=document.createElement("canvas"),a=s.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const h=(window.devicePixelRatio||1)*n;s.width=t*h,s.height=i*h,a.scale(h,h),a.font=e.font,a.fillStyle=e.color,a.textBaseline="top",a.globalAlpha=1;const c=U(a,r,t),p=e.lineHeight;c.forEach((w,E)=>{const C=E*p+2;let x=0;e.textAlign==="center"?x=t/2:e.textAlign==="right"&&(x=t),a.textAlign=e.textAlign,a.fillText(w,x,C)});const g=new u.CanvasTexture(s);return g.colorSpace=u.SRGBColorSpace,g.minFilter=u.LinearFilter,g.magFilter=u.LinearFilter,g.needsUpdate=!0,g}function b(r){return typeof r=="number"?r:parseFloat(r)||0}function k(r,e){var o,h,c,p;if(!e){r.set(0,0,0,0);return}const t=e.split("/")[0].trim().split(/\s+/),i=b(t[0]),n=b((o=t[1])!=null?o:t[0]),s=b((h=t[2])!=null?h:t[0]),a=b((p=(c=t[3])!=null?c:t[1])!=null?p:t[0]);r.set(i,n,s,a)}const W=`
3
3
  varying vec2 vUv;
4
4
  void main() {
5
5
  vUv = uv;
6
6
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
7
7
  }
8
- `,N=`
8
+ `,H=`
9
+
9
10
  varying vec2 vUv;
10
11
 
11
12
  uniform vec2 uSize;
12
- uniform float uRadius;
13
+ uniform vec4 uRadius;
13
14
  uniform float uBorderWidth;
14
- uniform vec3 uColor;
15
+ uniform vec3 uBgColor;
15
16
  uniform vec3 uBorderColor;
16
17
  uniform float uOpacity;
17
18
  uniform float uBgOpacity;
19
+ uniform float uBorderOpacity;
18
20
 
19
- // SDF 박스 함수
21
+ // SDF box
20
22
  float sdRoundedBox(vec2 p, vec2 b, float r) {
21
23
  vec2 q = abs(p) - b + r;
24
+ // return length(max(q, 0.0)) - r;
22
25
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
23
26
  }
24
27
 
25
28
  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)
26
31
  vec2 p = (vUv - 0.5) * uSize;
27
32
  vec2 halfSize = uSize * 0.5;
28
33
 
29
- float d = sdRoundedBox(p, halfSize, uRadius);
30
-
31
- float smoothEdge = 1.0;
34
+ // # border-radius
35
+ vec2 xRadii = mix(uRadius.xw, uRadius.yz, step(0.0, p.x));
36
+ float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
37
+ float d = sdRoundedBox(p, halfSize, r);
32
38
 
33
- float fillAlpha = 1.0 - smoothstep(-uBorderWidth - smoothEdge, -uBorderWidth, d);
34
-
35
- float borderAlpha = 0.0;
36
-
37
- if (uBorderWidth > 0.01) {
38
- borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
39
- }
39
+ // 1px blur for anti-aliasing
40
+ float aa = 1.0;
40
41
 
41
- vec3 color = uColor;
42
- float totalAlpha = borderAlpha + fillAlpha;
43
-
44
- if (totalAlpha > 0.001) {
45
- color = mix(uColor, uBorderColor, borderAlpha / totalAlpha);
42
+ // x == 0~aa -> 1
43
+ // x < 0 -> 0
44
+ // x > aa -> 1
45
+ float bgMask = 1.0 - smoothstep(0.0, aa, d);
46
+
47
+ // v valley
48
+ // 10px -> 5
49
+ float halfBorder = uBorderWidth * 0.5;
50
+ // 10px :
51
+ // 0 -> 0
52
+ // -10 -> 0
53
+ // -5 -> -5
54
+ float borderD = abs(d + halfBorder) - halfBorder;
55
+ // ^ border 내부는 음수/외부는 양수
56
+
57
+ // [!] 내부가 음수인 곳에서 시작점이 0이면 내부는 안티 얼리어싱 되지 않음.
58
+ // 1에서 빼든 그대로든 상관없이 경계선 밖 1px
59
+
60
+ // 최종 alpha를 위해 border 부분에 해당하는 픽셀 만 1로 반환.
61
+ float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
62
+
63
+ if (uBorderWidth <= 0.01) {
64
+ borderAlpha = 0.0;
46
65
  }
47
-
48
- float shapeAlpha = borderAlpha + (fillAlpha * uBgOpacity);
49
- float finalOpacity = shapeAlpha * uOpacity;
50
-
66
+
67
+ float aFront = borderAlpha;
68
+ float aBack = uBgOpacity;
69
+ float aOut = aFront + aBack * (1.0 - aFront);
70
+
71
+ float safeAlpha = max(aOut, 0.0001);
72
+ vec3 cOut = (uBorderColor * aFront + uBgColor * aBack * (1.0 - aFront)) / safeAlpha;
73
+
74
+ float finalOpacity = aOut * bgMask * uOpacity;
75
+
51
76
  if (finalOpacity < 0.001) discard;
52
77
 
53
- gl_FragColor = vec4(color, finalOpacity);
78
+ gl_FragColor = vec4(cOut, finalOpacity);
79
+
80
+ #include <colorspace_fragment>
54
81
  }
55
- `;function E(r){if(!r||r==="transparent")return{color:new h.Color(16777215),alpha:0};const t=r.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(t){const e=parseInt(t[1],10),i=parseInt(t[2],10),n=parseInt(t[3],10),s=t[4]!==void 0?parseFloat(t[4]):1;return{color:new h.Color(`rgb(${e}, ${i}, ${n})`),alpha:s}}return{color:new h.Color(r),alpha:1}}function L(r,t,e){var o;const i=E(r.backgroundColor),n=E(r.borderColor),s={uSize:{value:new h.Vector2(t,e)},uRadius:{value:S(r.borderRadius)},uBorderWidth:{value:S(r.borderWidth)},uColor:{value:i.color},uBorderColor:{value:n.color},uOpacity:{value:(o=r.opacity)!=null?o:1},uBgOpacity:{value:i.alpha}};return new h.ShaderMaterial({uniforms:s,vertexShader:F,fragmentShader:N,transparent:!0,side:h.FrontSide})}function A(r,t,e,i){var a;const n=E(t.backgroundColor),s=E(t.borderColor);r.uniforms.uSize.value.set(e,i),r.uniforms.uRadius.value=S(t.borderRadius),r.uniforms.uBorderWidth.value=S(t.borderWidth),r.uniforms.uColor.value.copy(n.color),r.uniforms.uBorderColor.value.copy(s.color),r.uniforms.uOpacity.value=(a=t.opacity)!=null?a:1,r.uniforms.uBgOpacity.value=n.alpha}const D={create(r,t,e,i,n,s=2){if(r==="BOX")return L(t,i,n);if(r==="TEXT"){const a=O(e||"",t,i,n,s);return new h.MeshBasicMaterial({map:a,transparent:!0,side:h.FrontSide,color:16777215})}return new h.MeshBasicMaterial({visible:!1})},update(r,t,e,i,n,s,a=2){if(t==="BOX")A(r,e,n,s);else if(t==="TEXT"){const o=r;o.map&&o.map.dispose();const l=O(i||"",e,n,s,a);o.map=l,o.needsUpdate=!0}}};class X{constructor(t,e,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,"customZIndex","9999");c(this,"target");c(this,"mountContainer");c(this,"targetRect");c(this,"meshMap",new Map);var a,o,l;this.target=t,this.mountContainer=i,this.mode=(a=e.mode)!=null?a:"overlay",(o=e.style)!=null&&o.zIndex&&(this.customZIndex=e.style.zIndex),this.canvas=document.createElement("canvas"),this.scene=new h.Scene,this.targetRect=this.target.getBoundingClientRect();const n=this.targetRect.width,s=this.targetRect.height;this.camera=new h.OrthographicCamera(n/-2,n/2,s/2,s/-2,1,1e3),this.camera.position.z=100,this.renderer=new h.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(n,s),this.applyTextQuality((l=e.textQuality)!=null?l:"medium")}applyTextQuality(t){if(typeof t=="number"){this.textQualityFactor=Math.max(.1,t);return}switch(t){case"low":this.textQualityFactor=1;break;case"high":this.textQualityFactor=4;break;case"medium":default:this.textQualityFactor=2;break}}mount(){this.mountContainer.appendChild(this.canvas),this.canvas.style.zIndex=this.customZIndex,this.canvas.style.pointerEvents=this.mode==="overlay"?"none":"auto",this.updateCanvasLayout()}updateCanvasLayout(){this.canvas.style.width=`${this.targetRect.width}px`,this.canvas.style.height=`${this.targetRect.height}px`,this.mode==="duplicate"?(this.canvas.style.position="",this.canvas.style.top="",this.canvas.style.left="",this.canvas.style.display="block"):(this.canvas.style.position="absolute",this.canvas.style.top=`${this.target.offsetTop}px`,this.canvas.style.left=`${this.target.offsetLeft}px`,this.canvas.style.display="block")}dispose(){this.renderer.dispose(),this.canvas.remove()}setSize(t,e){this.renderer.setSize(t,e),this.camera.left=t/-2,this.camera.right=t/2,this.camera.top=e/2,this.camera.bottom=e/-2,this.camera.updateProjectionMatrix()}syncScene(t){const e=this.target.getBoundingClientRect(),i=Math.abs(e.width-this.targetRect.width)>.1||Math.abs(e.height-this.targetRect.height)>.1,n=this.mode==="overlay"&&(Math.abs(e.top-this.targetRect.top)>.1||Math.abs(e.left-this.targetRect.left)>.1);i?(this.targetRect=e,this.renderer.setSize(this.targetRect.width,this.targetRect.height),this.camera.left=this.targetRect.width/-2,this.camera.right=this.targetRect.width/2,this.camera.top=this.targetRect.height/2,this.camera.bottom=this.targetRect.height/-2,this.camera.updateProjectionMatrix(),this.updateCanvasLayout()):n?(this.targetRect=e,this.updateCanvasLayout()):this.targetRect=e,this.renderOrder=0;const s=new Set;this.reconcileNode(t,s);for(const[a,o]of this.meshMap.entries())s.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof h.Material&&o.material.dispose(),this.meshMap.delete(a))}reconcileNode(t,e){e.add(t.element);let i=this.meshMap.get(t.element);if(!i){const n=new h.PlaneGeometry(1,1),s=D.create("BOX",t.styles,"",t.rect.width,t.rect.height);i=new h.Mesh(n,s),t.type==="TEXT"&&(i.name="BG_MESH"),this.scene.add(i),this.meshMap.set(t.element,i)}if(i.userData.domRect=t.rect,this.updateMeshProperties(i,t),t.type==="BOX")for(const n of t.children)this.reconcileNode(n,e);else t.type==="TEXT"&&this.reconcileTextChild(i,t)}reconcileTextChild(t,e){var o,l;let i=t.children.find(u=>u.name==="TEXT_CHILD");const n=JSON.stringify(e.textStyles),s=(o=i==null?void 0:i.userData)==null?void 0:o.styleHash;if(!i||e.dirtyMask&M||n!==s){i&&((l=i.material.map)==null||l.dispose(),i.geometry.dispose(),t.remove(i));const u=D.create("TEXT",e.textStyles,e.textContent||"",e.rect.width,e.rect.height,this.textQualityFactor),m=new h.PlaneGeometry(1,1);i=new h.Mesh(m,u),i.name="TEXT_CHILD",i.userData={styleHash:n},i.position.z=.005,t.add(i)}if(i){const u=t.userData.domRect,m=u.x+u.width/2,p=u.y+u.height/2,g=e.rect.x+e.rect.width/2,y=e.rect.y+e.rect.height/2,w=g-m,v=-(y-p);i.position.set(w,v,.005)}}updateMeshProperties(t,e){const{rect:i,styles:n}=e,s=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/s,o=this.renderer.domElement.height/s;t.scale.set(i.width,i.height,1);const l=.001;this.renderOrder++;const u=this.targetRect.left+window.scrollX,m=this.targetRect.top+window.scrollY,p=i.x-u,g=i.y-m;t.position.set(p-a/2+i.width/2,-g+o/2-i.height/2,n.zIndex+this.renderOrder*l),D.update(t.material,"BOX",e.styles,"",e.rect.width,e.rect.height)}render(){this.renderer.render(this.scene,this.camera)}}function _(r){const t=document.createRange();t.selectNodeContents(r);const e=t.getBoundingClientRect();return{left:e.left,top:e.top,width:e.width,height:e.height}}function Y(r){const t=parseFloat(r.fontSize);let e=parseFloat(r.lineHeight);isNaN(e)&&(e=t*1.2);let i=parseFloat(r.letterSpacing);return isNaN(i)&&(i=0),{font:`${r.fontStyle} ${r.fontWeight} ${r.fontSize} ${r.fontFamily}`,color:r.color,textAlign:r.textAlign||"start",textBaseline:"alphabetic",direction:r.direction||"inherit",lineHeight:e,letterSpacing:i}}function z(r,t=R|b|B|M|C){if(r.nodeType===Node.TEXT_NODE){const p=r;if(!p.textContent||!p.textContent.trim())return null;const g=p.textContent.replace(/\s+/g," ").trim();if(g.length===0)return null;const y=_(p);if(y.width===0||y.height===0)return null;const w=p.parentElement,v=w?window.getComputedStyle(w):null;return v?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:p,rect:{x:y.left+window.scrollX,y:y.top+window.scrollY,width:y.width,height:y.height},styles:{backgroundColor:"transparent",opacity:parseFloat(v.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:g,textStyles:Y(v),dirtyMask:t,children:[]}:null}const e=r,i=e.getBoundingClientRect(),n=window.getComputedStyle(e);if(i.width===0||i.height===0||n.display==="none")return null;let s=e.getAttribute("data-mid");s||(s=Math.random().toString(36).substring(2,11),e.setAttribute("data-mid",s));const a=parseInt(n.zIndex),o={backgroundColor:n.backgroundColor,opacity:parseFloat(n.opacity),zIndex:isNaN(a)?0:a,borderRadius:n.borderRadius,borderColor:n.borderColor,borderWidth:n.borderWidth};let l,u;const m=[];return Array.from(e.childNodes).forEach(p=>{const g=z(p,t);g&&m.push(g)}),{id:s,type:"BOX",element:e,rect:{x:i.left+window.scrollX,y:i.top+window.scrollY,width:i.width,height:i.height},styles:o,textContent:l,textStyles:u,dirtyMask:t,children:m}}class k{constructor(t,e){c(this,"target");c(this,"renderer");c(this,"observer");c(this,"isDomDirty",!1);c(this,"isRunning",!1);c(this,"pendingMask",x);c(this,"mutationTimer",null);c(this,"cssTimer",null);c(this,"onTransitionFinished",t=>{this.target.contains(t.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=R|b,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=t,this.renderer=e,this.observer=new MutationObserver(i=>{let n=x;for(const s of i)s.type==="childList"?n|=C:s.type==="attributes"&&(s.attributeName==="style"||s.attributeName==="class")&&(n|=R|b);if(n!==x){if(this.pendingMask|=n,n&C){this.clearTimers(),console.log("Structural Change detected"),this.isDomDirty=!0;return}this.mutationTimer&&clearTimeout(this.mutationTimer),this.mutationTimer=window.setTimeout(()=>{this.mutationTimer=null,this.isDomDirty=!0},200)}})}start(){this.isRunning||(this.isRunning=!0,this.observer.observe(this.target,{childList:!0,subtree:!0,attributes:!0,characterData:!0}),this.target.addEventListener("transitionend",this.onTransitionFinished),this.target.addEventListener("animationend",this.onTransitionFinished),window.addEventListener("resize",this.onWindowResize),this.forceUpdateScene(),this.renderLoop())}stop(){this.isRunning=!1,this.observer.disconnect(),this.clearTimers(),this.target.removeEventListener("transitionend",this.onTransitionFinished),this.target.removeEventListener("animationend",this.onTransitionFinished),window.removeEventListener("resize",this.onWindowResize)}clearTimers(){this.mutationTimer&&(clearTimeout(this.mutationTimer),this.mutationTimer=null),this.cssTimer&&(clearTimeout(this.cssTimer),this.cssTimer=null)}forceUpdateScene(){this.isDomDirty=!1;const t=z(this.target,this.pendingMask);t&&this.renderer.syncScene(t),this.pendingMask=x}}class P{constructor(t,e){c(this,"renderer");c(this,"syncer");c(this,"target");var n,s,a;this.target=t;let i;if(e.mode==="duplicate"?i=(s=(n=e.container)!=null?n:this.target.parentElement)!=null?s: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 X(this.target,e,i),this.renderer.mount(),this.syncer=new k(this.target,this.renderer)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}d.DIRTY_CONTENT=M,d.DIRTY_NONE=x,d.DIRTY_RECT=R,d.DIRTY_STRUCTURE=C,d.DIRTY_STYLE=b,d.DIRTY_ZINDEX=B,d.Engine=P,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
82
+ `;function O(r){if(!r||r==="transparent")return{color:new u.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),n=parseInt(e[3],10),s=e[4]!==void 0?parseFloat(e[4]):1;return{color:new u.Color(`rgb(${t}, ${i}, ${n})`),alpha:s}}return{color:new u.Color(r),alpha:1}}function $(r,e,t){var o;const i=O(r.backgroundColor),n=O(r.borderColor),s={uSize:{value:new u.Vector2(e,t)},uRadius:{value:new u.Vector4(0,0,0,0)},uBorderWidth:{value:b(r.borderWidth)},uBgColor:{value:i.color},uBorderColor:{value:n.color},uOpacity:{value:(o=r.opacity)!=null?o:1},uBgOpacity:{value:i.alpha},uBorderOpacity:{value:n.alpha}};return k(s.uRadius.value,r.borderRadius),new u.ShaderMaterial({uniforms:s,vertexShader:W,fragmentShader:H,transparent:!0,side:u.FrontSide})}function j(r,e,t,i){var a;const n=O(e.backgroundColor),s=O(e.borderColor);r.uniforms.uSize.value.set(t,i),k(r.uniforms.uRadius.value,e.borderRadius),r.uniforms.uBorderWidth.value=b(e.borderWidth),r.uniforms.uBgColor.value.copy(n.color),r.uniforms.uBorderColor.value.copy(s.color),r.uniforms.uOpacity.value=(a=e.opacity)!=null?a:1,r.uniforms.uBgOpacity.value=n.alpha,r.uniforms.uBorderOpacity.value=s.alpha}const N={create(r,e,t,i,n,s=2){if(r==="BOX")return $(e,i,n);if(r==="TEXT"){const a=_(t||"",e,i,n,s);return new u.MeshBasicMaterial({map:a,transparent:!0,side:u.FrontSide,color:16777215})}return new u.MeshBasicMaterial({visible:!1})},update(r,e,t,i,n,s,a=2){if(e==="BOX")j(r,t,n,s);else if(e==="TEXT"){const o=r;o.map&&o.map.dispose();const h=_(i||"",t,n,s,a);o.map=h,o.needsUpdate=!0}}};class Q{constructor(e,t,i){l(this,"canvas");l(this,"scene");l(this,"camera");l(this,"renderer");l(this,"renderOrder",0);l(this,"textQualityFactor",2);l(this,"mode","overlay");l(this,"target");l(this,"mountContainer");l(this,"targetRect");l(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 u.Scene,this.targetRect=this.target.getBoundingClientRect();const n=this.targetRect.width,s=this.targetRect.height;this.camera=new u.OrthographicCamera(n/-2,n/2,s/2,s/-2,1,1e3),this.camera.position.z=100,this.renderer=new u.WebGLRenderer({canvas:this.canvas,alpha:!0,antialias:!0}),this.renderer.setPixelRatio(window.devicePixelRatio),this.renderer.setSize(n,s),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,n=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()):n?(this.targetRect=t,this.updateCanvasLayout()):this.targetRect=t,this.renderOrder=0;const s=new Set;this.reconcileNode(e,s);for(const[a,o]of this.meshMap.entries())s.has(a)||(this.scene.remove(o),o.geometry.dispose(),o.material instanceof u.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 n=new u.PlaneGeometry(1,1),s=N.create("BOX",e.styles,"",e.rect.width,e.rect.height);i=new u.Mesh(n,s),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 n of e.children)this.reconcileNode(n,t);else e.type==="TEXT"&&this.reconcileTextChild(i,e)}reconcileTextChild(e,t){var o,h;let i=e.children.find(c=>c.name==="TEXT_CHILD");const n=JSON.stringify(t.textStyles),s=(o=i==null?void 0:i.userData)==null?void 0:o.styleHash;if(!i||t.dirtyMask&F||n!==s){i&&((h=i.material.map)==null||h.dispose(),i.geometry.dispose(),e.remove(i));const c=N.create("TEXT",t.textStyles,t.textContent||"",t.rect.width,t.rect.height,this.textQualityFactor),p=new u.PlaneGeometry(1,1);i=new u.Mesh(p,c),i.name="TEXT_CHILD",i.userData={styleHash:n},i.layers.set(t.visibility),i.position.z=.005,e.add(i)}if(i){const c=e.userData.domRect,p=c.x+c.width/2,g=c.y+c.height/2,w=t.rect.x+t.rect.width/2,E=t.rect.y+t.rect.height/2,C=w-p,x=-(E-g);i.position.set(C,x,.005)}}updateMeshProperties(e,t){const{rect:i,styles:n}=t,s=this.renderer.getPixelRatio(),a=this.renderer.domElement.width/s,o=this.renderer.domElement.height/s;e.scale.set(i.width,i.height,1);const h=.001;this.renderOrder++;const c=this.targetRect.left+window.scrollX,p=this.targetRect.top+window.scrollY,g=i.x-c,w=i.y-p;e.position.set(g-a/2+i.width/2,-w+o/2-i.height/2,n.zIndex+this.renderOrder*h),N.update(e.material,"BOX",t.styles,"",t.rect.width,t.rect.height)}render(){this.renderer.render(this.scene,this.camera)}}function G(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 q(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 Y(r,e=S|D|I|F|B,t,i){if(r.nodeType===Node.TEXT_NODE){const f=r;if(!f.textContent||!f.textContent.trim())return null;const y=f.textContent.replace(/\s+/g," ").trim();if(y.length===0)return null;const v=G(f);if(v.width===0||v.height===0)return null;const P=f.parentElement,z=P?window.getComputedStyle(P):null;return z?{id:Math.random().toString(36).substring(2,9),type:"TEXT",element:f,rect:{x:v.left+window.scrollX,y:v.top+window.scrollY,width:v.width,height:v.height},styles:{backgroundColor:"transparent",opacity:parseFloat(z.opacity),zIndex:0,borderRadius:"0px",borderColor:"transparent",borderWidth:"0px"},textContent:y,textStyles:q(z),dirtyMask:e,visibility:t,children:[]}:null}const n=r,s=n.dataset.mirageFilter;let a=t,o=t;if(s){const f=new Set(s.split(/\s+/));for(const y of f)if(!X.includes(y))throw new Error(`[MirageEngine] Invalid filter token: '${y}'. 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=M:f.has("exclude-tree")&&(a=L),o=a,f.has("include-self")?o=M:f.has("exclude-self")&&(o=L)}if(i&&i.end&&i.end.length>0&&i.end.some(y=>n.classList.contains(y)))return null;const h=n.getBoundingClientRect(),c=window.getComputedStyle(n);if(h.width===0||h.height===0||c.display==="none")return null;let p=n.getAttribute("data-mid");p||(p=Math.random().toString(36).substring(2,11),n.setAttribute("data-mid",p));const g=parseInt(c.zIndex),w={backgroundColor:c.backgroundColor,opacity:parseFloat(c.opacity),zIndex:isNaN(g)?0:g,borderRadius:c.borderRadius,borderColor:c.borderColor,borderWidth:c.borderWidth};let E,C;const x=[];return Array.from(n.childNodes).forEach(f=>{const y=f.nodeType===Node.TEXT_NODE?o:a,v=Y(f,e,y,i);v&&x.push(v)}),{id:p,type:"BOX",element:n,rect:{x:h.left+window.scrollX,y:h.top+window.scrollY,width:h.width,height:h.height},styles:w,textContent:E,textStyles:C,dirtyMask:e,visibility:o,children:x}}class V{constructor(e,t,i){l(this,"target");l(this,"renderer");l(this,"filter");l(this,"observer");l(this,"isDomDirty",!1);l(this,"isRunning",!1);l(this,"pendingMask",R);l(this,"mutationTimer",null);l(this,"cssTimer",null);l(this,"onTransitionFinished",e=>{this.target.contains(e.target)&&this.mutationTimer===null&&(this.cssTimer&&clearTimeout(this.cssTimer),this.pendingMask|=S|D,this.cssTimer=window.setTimeout(()=>{this.isDomDirty=!0,this.cssTimer=null},50))});l(this,"onWindowResize",()=>{this.renderer.setSize(window.innerWidth,window.innerHeight),this.isDomDirty=!0});l(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(n=>{let s=R;for(const a of n)a.type==="childList"?s|=B:a.type==="attributes"&&(a.attributeName==="style"||a.attributeName==="class")&&(s|=S|D);if(s!==R){if(this.pendingMask|=s,s&B){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=Y(this.target,this.pendingMask,M,this.filter);e&&this.renderer.syncScene(e),this.pendingMask=R}}class Z{constructor(e,t){l(this,"renderer");l(this,"syncer");l(this,"target");var n,s,a;this.target=e;let i;if(t.mode==="duplicate"?i=(s=(n=t.container)!=null?n:this.target.parentElement)!=null?s: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 Q(this.target,t,i),this.renderer.mount(),this.syncer=new V(this.target,this.renderer,t.filter)}start(){this.syncer.start()}stop(){this.syncer.stop()}dispose(){this.syncer.stop(),this.renderer.dispose()}}d.ALLOWED_FILTERS=X,d.DIRTY_CONTENT=F,d.DIRTY_NONE=R,d.DIRTY_RECT=S,d.DIRTY_STRUCTURE=B,d.DIRTY_STYLE=D,d.DIRTY_ZINDEX=I,d.EXCLUDED=L,d.Engine=Z,d.INCLUDED=M,d.SYSTEM=A,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})});
@@ -1,15 +1,17 @@
1
+ import { FilterConfig } from '../types/config';
1
2
  import { Renderer } from '../renderer/Renderer';
2
3
 
3
4
  export declare class Syncer {
4
5
  private target;
5
6
  private renderer;
7
+ private filter?;
6
8
  private observer;
7
9
  private isDomDirty;
8
10
  private isRunning;
9
11
  private pendingMask;
10
12
  private mutationTimer;
11
13
  private cssTimer;
12
- constructor(target: HTMLElement, renderer: Renderer);
14
+ constructor(target: HTMLElement, renderer: Renderer, filter?: FilterConfig);
13
15
  start(): void;
14
16
  stop(): void;
15
17
  private clearTimers;
@@ -1,3 +1,4 @@
1
- import { SceneNode } from '../types';
1
+ import { SceneNode, Visibility } from '../types';
2
+ import { FilterConfig } from '../types/config';
2
3
 
3
- export declare function extractSceneGraph(sourceNode: HTMLElement | Node, initialMask?: number): SceneNode | null;
4
+ export declare function extractSceneGraph(sourceNode: HTMLElement | Node, initialMask: number | undefined, inheritedFlow: Visibility, filterConfig?: FilterConfig): SceneNode | null;
@@ -8,7 +8,6 @@ export declare class Renderer {
8
8
  private renderOrder;
9
9
  private textQualityFactor;
10
10
  private mode;
11
- private customZIndex;
12
11
  private target;
13
12
  private mountContainer;
14
13
  private targetRect;
@@ -1,4 +1,5 @@
1
1
  import { TextStyles, BoxStyles } from '../../packages/painter/src/index.ts';
2
+ import { Visibility } from './flags';
2
3
 
3
4
  export type NodeType = "BOX" | "TEXT";
4
5
  export interface NodeRect {
@@ -16,5 +17,6 @@ export interface SceneNode {
16
17
  textContent?: string;
17
18
  textStyles?: TextStyles;
18
19
  dirtyMask: number;
20
+ visibility: Visibility;
19
21
  children: SceneNode[];
20
22
  }
@@ -1,11 +1,19 @@
1
1
  export type TextQuality = "low" | "medium" | "high" | number;
2
2
  export type MirageMode = "overlay" | "duplicate";
3
+ export interface FilterConfig {
4
+ includeTree?: string[];
5
+ excludeTree?: string[];
6
+ includeSelf?: string[];
7
+ excludeSelf?: string[];
8
+ end?: string[];
9
+ }
3
10
  interface BaseConfig {
4
11
  debug?: boolean;
5
12
  textQuality?: TextQuality;
6
13
  style?: {
7
14
  zIndex?: string;
8
15
  };
16
+ filter?: FilterConfig;
9
17
  }
10
18
  export interface OverlayConfig extends BaseConfig {
11
19
  mode?: "overlay";
@@ -4,3 +4,8 @@ export declare const DIRTY_STYLE: number;
4
4
  export declare const DIRTY_ZINDEX: number;
5
5
  export declare const DIRTY_STRUCTURE: number;
6
6
  export declare const DIRTY_CONTENT: number;
7
+ export declare const INCLUDED = 0;
8
+ export declare const SYSTEM = 29;
9
+ export declare const EXCLUDED = 30;
10
+ export type Visibility = typeof EXCLUDED | typeof SYSTEM | typeof INCLUDED;
11
+ export declare const ALLOWED_FILTERS: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirage-engine/core",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "main": "src/index.ts",
5
5
  "types": "src/index.ts",
6
6
  "peerDependencies": {
@@ -27,7 +27,7 @@ export class Engine {
27
27
 
28
28
  this.renderer.mount();
29
29
 
30
- this.syncer = new Syncer(this.target, this.renderer);
30
+ this.syncer = new Syncer(this.target, this.renderer, config.filter);
31
31
  }
32
32
 
33
33
  public start() {
@@ -1,3 +1,4 @@
1
+ import { FilterConfig } from "../types/config";
1
2
  import { Renderer } from "../renderer/Renderer";
2
3
  import { extractSceneGraph } from "../dom/Extractor";
3
4
  import {
@@ -5,11 +6,14 @@ import {
5
6
  DIRTY_RECT,
6
7
  DIRTY_STRUCTURE,
7
8
  DIRTY_STYLE,
9
+ INCLUDED
8
10
  } from "../types";
9
11
 
10
12
  export class Syncer {
11
13
  private target: HTMLElement;
12
14
  private renderer: Renderer;
15
+ private filter?: FilterConfig;
16
+
13
17
  private observer: MutationObserver;
14
18
 
15
19
  private isDomDirty: boolean = false;
@@ -20,9 +24,10 @@ export class Syncer {
20
24
  private mutationTimer: number | null = null;
21
25
  private cssTimer: number | null = null;
22
26
 
23
- constructor(target: HTMLElement, renderer: Renderer) {
27
+ constructor(target: HTMLElement, renderer: Renderer, filter?: FilterConfig) {
24
28
  this.target = target;
25
29
  this.renderer = renderer;
30
+ this.filter = filter;
26
31
 
27
32
  this.observer = new MutationObserver((mutations) => {
28
33
  let currentMask = DIRTY_NONE;
@@ -126,7 +131,7 @@ export class Syncer {
126
131
 
127
132
  private forceUpdateScene() {
128
133
  this.isDomDirty = false;
129
- const sceneGraph = extractSceneGraph(this.target, this.pendingMask);
134
+ const sceneGraph = extractSceneGraph(this.target, this.pendingMask, INCLUDED, this.filter);
130
135
 
131
136
  if (sceneGraph) {
132
137
  this.renderer.syncScene(sceneGraph);
@@ -5,9 +5,14 @@ import {
5
5
  DIRTY_ZINDEX,
6
6
  DIRTY_STRUCTURE,
7
7
  SceneNode,
8
+ Visibility,
9
+ EXCLUDED,
10
+ INCLUDED,
11
+ ALLOWED_FILTERS,
8
12
  } from "../types";
9
13
 
10
14
  import { BoxStyles, TextStyles } from "@mirage-engine/painter";
15
+ import { FilterConfig } from "../types/config";
11
16
 
12
17
  // Helper function: getTextNodeRect, isValidTextNode, isLeafTextElement, extractTextStyles
13
18
 
@@ -50,12 +55,13 @@ export function extractSceneGraph(
50
55
  DIRTY_STYLE |
51
56
  DIRTY_ZINDEX |
52
57
  DIRTY_CONTENT |
53
- DIRTY_STRUCTURE
58
+ DIRTY_STRUCTURE,
59
+ inheritedFlow: Visibility,
60
+ filterConfig?: FilterConfig,
54
61
  ): SceneNode | null {
55
62
  // Check text node
56
63
  if (sourceNode.nodeType === Node.TEXT_NODE) {
57
64
  const textNode = sourceNode as Text;
58
-
59
65
  // empthy text check
60
66
  if (!textNode.textContent || !textNode.textContent.trim()) return null;
61
67
  const normalizedText = textNode.textContent.replace(/\s+/g, " ").trim();
@@ -93,11 +99,66 @@ export function extractSceneGraph(
93
99
  textContent: normalizedText,
94
100
  textStyles: extractTextStyles(computed),
95
101
  dirtyMask: initialMask,
102
+ visibility: inheritedFlow,
96
103
  children: [],
97
104
  };
98
105
  }
99
106
 
100
107
  const element = sourceNode as HTMLElement;
108
+ // [[Filter]] data attribute based filtering
109
+ const filterData = element.dataset.mirageFilter;
110
+ let visibleFlow = inheritedFlow;
111
+ let visibleFlag = inheritedFlow;
112
+ if (filterData) {
113
+ const filterSet = new Set(filterData.split(/\s+/));
114
+ // error check
115
+ for (const token of filterSet) {
116
+ if (!ALLOWED_FILTERS.includes(token)) {
117
+ throw new Error(
118
+ `[MirageEngine] Invalid filter token: '${token}'. ` +
119
+ `Expected one of: 'include-tree', 'exclude-tree', 'include-self', 'exclude-self', 'end'.`,
120
+ );
121
+ }
122
+ }
123
+
124
+ if (filterSet.has("end")) return null;
125
+
126
+ // error check
127
+ if (filterSet.has("include-tree") && filterSet.has("exclude-tree")) {
128
+ throw new Error(
129
+ `[MirageEngine] Conflicting filters: 'include-tree' and 'exclude-tree' cannot be used together on the same element.`,
130
+ );
131
+ }
132
+ if (filterSet.has("include-self") && filterSet.has("exclude-self")) {
133
+ throw new Error(
134
+ `[MirageEngine] Conflicting filters: 'include-self' and 'exclude-self' cannot be used together on the same element.`,
135
+ );
136
+ }
137
+
138
+ if (filterSet.has("include-tree")) {
139
+ visibleFlow = INCLUDED;
140
+ } else if (filterSet.has("exclude-tree")) {
141
+ visibleFlow = EXCLUDED;
142
+ }
143
+
144
+ visibleFlag = visibleFlow;
145
+
146
+ if (filterSet.has("include-self")) {
147
+ visibleFlag = INCLUDED;
148
+ } else if (filterSet.has("exclude-self")) {
149
+ visibleFlag = EXCLUDED;
150
+ }
151
+ }
152
+
153
+ // [[filter]] class based filtering
154
+ // [Filter] end
155
+ if (filterConfig && filterConfig.end && filterConfig.end.length > 0) {
156
+ const isEnd = filterConfig.end.some((cls) =>
157
+ element.classList.contains(cls),
158
+ );
159
+ if (isEnd) return null;
160
+ }
161
+
101
162
  const rect = element.getBoundingClientRect();
102
163
  const computed = window.getComputedStyle(element);
103
164
 
@@ -127,8 +188,14 @@ export function extractSceneGraph(
127
188
  const children: SceneNode[] = [];
128
189
 
129
190
  Array.from(element.childNodes).forEach((child) => {
130
- // Recurring
131
- const childNode = extractSceneGraph(child, initialMask);
191
+ const visibleFlowToPass =
192
+ child.nodeType === Node.TEXT_NODE ? visibleFlag : visibleFlow;
193
+ const childNode = extractSceneGraph(
194
+ child,
195
+ initialMask,
196
+ visibleFlowToPass,
197
+ filterConfig,
198
+ );
132
199
  if (childNode) {
133
200
  children.push(childNode);
134
201
  }
@@ -148,6 +215,7 @@ export function extractSceneGraph(
148
215
  textContent,
149
216
  textStyles,
150
217
  dirtyMask: initialMask,
218
+ visibility: visibleFlag,
151
219
  children,
152
220
  };
153
221
  }
@@ -16,7 +16,6 @@ export class Renderer {
16
16
  private renderOrder: number = 0;
17
17
  private textQualityFactor: number = 2;
18
18
  private mode: MirageMode = "overlay";
19
- private customZIndex: string = "9999";
20
19
 
21
20
  private target: HTMLElement;
22
21
  private mountContainer: HTMLElement;
@@ -34,10 +33,6 @@ export class Renderer {
34
33
 
35
34
  this.mode = config.mode ?? "overlay";
36
35
 
37
- if (config.style?.zIndex) {
38
- this.customZIndex = config.style.zIndex;
39
- }
40
-
41
36
  this.canvas = document.createElement("canvas");
42
37
  this.scene = new THREE.Scene();
43
38
 
@@ -59,10 +54,15 @@ export class Renderer {
59
54
  );
60
55
  this.camera.position.z = 100;
61
56
 
57
+ // [new]
58
+ // THREE.ColorManagement.enabled = false;
59
+
62
60
  this.renderer = new THREE.WebGLRenderer({
63
61
  canvas: this.canvas,
64
62
  alpha: true,
65
63
  antialias: true,
64
+ // [new]
65
+ // premultipliedAlpha: true
66
66
  });
67
67
 
68
68
  this.renderer.setPixelRatio(window.devicePixelRatio);
@@ -91,9 +91,8 @@ export class Renderer {
91
91
  }
92
92
 
93
93
  public mount() {
94
- this.mountContainer.appendChild(this.canvas);
94
+ this.mountContainer.prepend(this.canvas);
95
95
 
96
- this.canvas.style.zIndex = this.customZIndex;
97
96
  this.canvas.style.pointerEvents = this.mode === "overlay" ? "none" : "auto";
98
97
 
99
98
  this.updateCanvasLayout();
@@ -107,7 +106,6 @@ export class Renderer {
107
106
  this.canvas.style.position = "";
108
107
  this.canvas.style.top = "";
109
108
  this.canvas.style.left = "";
110
-
111
109
  this.canvas.style.display = "block";
112
110
  } else {
113
111
  this.canvas.style.position = "absolute";
@@ -154,6 +152,7 @@ export class Renderer {
154
152
  this.camera.right = this.targetRect.width / 2;
155
153
  this.camera.top = this.targetRect.height / 2;
156
154
  this.camera.bottom = this.targetRect.height / -2;
155
+ this.camera.layers.set(0);
157
156
  this.camera.updateProjectionMatrix();
158
157
 
159
158
  this.updateCanvasLayout();
@@ -174,7 +173,6 @@ export class Renderer {
174
173
  if (!activeElements.has(el)) {
175
174
  this.scene.remove(mesh);
176
175
 
177
- // Garbage Collection
178
176
  mesh.geometry.dispose();
179
177
  if (mesh.material instanceof THREE.Material) mesh.material.dispose();
180
178
  this.meshMap.delete(el);
@@ -197,8 +195,8 @@ export class Renderer {
197
195
  );
198
196
 
199
197
  mesh = new THREE.Mesh(geometry, material);
198
+ mesh.layers.set(node.visibility);
200
199
  if (node.type === "TEXT") mesh.name = "BG_MESH";
201
-
202
200
  this.scene.add(mesh);
203
201
  this.meshMap.set(node.element, mesh);
204
202
  }
@@ -249,7 +247,7 @@ export class Renderer {
249
247
 
250
248
  textMesh.name = "TEXT_CHILD";
251
249
  textMesh.userData = { styleHash: currentStyleHash };
252
-
250
+ textMesh.layers.set(node.visibility);
253
251
  textMesh.position.z = 0.005;
254
252
  parentMesh.add(textMesh);
255
253
  }
@@ -1,4 +1,5 @@
1
1
  import { TextStyles, BoxStyles } from "@mirage-engine/painter";
2
+ import { Visibility } from "./flags";
2
3
 
3
4
  export type NodeType = "BOX" | "TEXT"
4
5
 
@@ -9,8 +10,6 @@ export interface NodeRect {
9
10
  height: number;
10
11
  }
11
12
 
12
-
13
-
14
13
  export interface SceneNode {
15
14
  id: string;
16
15
  type: NodeType;
@@ -23,6 +22,10 @@ export interface SceneNode {
23
22
  textStyles?: TextStyles;
24
23
 
25
24
  dirtyMask: number;
25
+ // TODO: SceneNode의 sort 로직 추가(아마도)
26
+
27
+ visibility: Visibility;
28
+
26
29
  children: SceneNode[];
27
30
  }
28
31
 
@@ -1,21 +1,30 @@
1
1
  export type TextQuality = "low" | "medium" | "high" | number;
2
2
  export type MirageMode = "overlay" | "duplicate";
3
3
 
4
+ export interface FilterConfig {
5
+ includeTree?: string[];
6
+ excludeTree?: string[];
7
+ includeSelf?: string[];
8
+ excludeSelf?: string[];
9
+ end?: string[];
10
+ }
11
+
4
12
  interface BaseConfig {
5
13
  debug?: boolean;
6
14
  textQuality?: TextQuality;
7
15
  style?: {
8
16
  zIndex?: string;
9
17
  };
18
+ filter?: FilterConfig;
10
19
  }
11
20
 
12
21
  export interface OverlayConfig extends BaseConfig {
13
- mode?: "overlay";
22
+ mode?: "overlay";
14
23
  }
15
24
 
16
25
  export interface DuplicateConfig extends BaseConfig {
17
- mode: "duplicate";
18
- container?: HTMLElement;
26
+ mode: "duplicate";
27
+ container?: HTMLElement;
19
28
  }
20
29
 
21
- export type CoreConfig = OverlayConfig | DuplicateConfig;
30
+ export type CoreConfig = OverlayConfig | DuplicateConfig;
@@ -4,4 +4,20 @@ export const DIRTY_RECT = 1 << 0; // 1: x, y, width, height (00000001)
4
4
  export const DIRTY_STYLE = 1 << 1; // 2: style (00000010)
5
5
  export const DIRTY_ZINDEX = 1 << 2; // 4: zIndex (00000100)
6
6
  export const DIRTY_STRUCTURE = 1 << 3; // 8: childNode add / delete (00001000)
7
- export const DIRTY_CONTENT = 1 << 4; // 16: text content changed (00010000)
7
+ export const DIRTY_CONTENT = 1 << 4; // 16: text content changed (00010000)
8
+
9
+ // Filtering Flag
10
+ export const INCLUDED = 0;
11
+ export const SYSTEM = 29;
12
+ export const EXCLUDED = 30;
13
+
14
+ // 다른 파일에서 파라미터 타입 지정용으로 사용
15
+ export type Visibility = typeof EXCLUDED | typeof SYSTEM | typeof INCLUDED;
16
+
17
+ export const ALLOWED_FILTERS = [
18
+ "include-tree",
19
+ "exclude-tree",
20
+ "include-self",
21
+ "exclude-self",
22
+ "end",
23
+ ];