@mirage-engine/painter 0.4.0 → 1.0.1

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,85 +1,151 @@
1
- (function(s,v){typeof exports=="object"&&typeof module<"u"?v(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],v):(s=typeof globalThis<"u"?globalThis:s||self,v(s.MiragePainter={},s.THREE))})(this,function(s,v){"use strict";function S(e){const r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const o in e)if(o!=="default"){const i=Object.getOwnPropertyDescriptor(e,o);Object.defineProperty(r,o,i.get?i:{enumerable:!0,get:()=>e[o]})}}return r.default=e,Object.freeze(r)}const c=S(v);function E(e,r,o){const i=r.split(`
2
- `),t=[];return i.forEach(n=>{const a=n.split(" ");let l=a[0];for(let u=1;u<a.length;u++){const d=a[u];e.measureText(l+" "+d).width<o?l+=" "+d:(t.push(l),l=d)}t.push(l)}),t}function m(e,r,o,i,t=2){const n=document.createElement("canvas"),a=n.getContext("2d");if(!a)throw new Error("[Mirage] Failed to create canvas context");const u=(window.devicePixelRatio||1)*t;n.width=o*u,n.height=i*u,a.scale(u,u),a.font=r.font,a.fillStyle=r.color,a.textBaseline="top",a.globalAlpha=1;const d=E(a,e,o),x=r.lineHeight;d.forEach((b,B)=>{const R=B*x+2;let C=0;r.textAlign==="center"?C=o/2:r.textAlign==="right"&&(C=o),a.textAlign=r.textAlign,a.fillText(b,C,R)});const f=new c.CanvasTexture(n);return f.colorSpace=c.SRGBColorSpace,f.minFilter=c.LinearFilter,f.magFilter=c.LinearFilter,f.needsUpdate=!0,f}function p(e){return typeof e=="number"?e:parseFloat(e)||0}function h(e,r){if(!r){e.set(0,0,0,0);return}const o=r.split("/")[0].trim().split(/\s+/),i=p(o[0]),t=p(o[1]??o[0]),n=p(o[2]??o[0]),a=p(o[3]??o[1]??o[0]);e.set(i,t,n,a)}const M=`
3
- varying vec2 vUv;
4
- varying vec4 vScreenPos;
5
- void main() {
6
- vUv = uv;
7
- gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
8
- vScreenPos = gl_Position;
9
- }
10
- `,w=`
11
-
12
- varying vec2 vUv;
1
+ (function(f,h){typeof exports=="object"&&typeof module<"u"?h(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],h):(f=typeof globalThis<"u"?globalThis:f||self,h(f.MiragePainter={},f.THREE))})(this,function(f,h){"use strict";var N=Object.defineProperty;var q=(f,h,C)=>h in f?N(f,h,{enumerable:!0,configurable:!0,writable:!0,value:C}):f[h]=C;var O=(f,h,C)=>(q(f,typeof h!="symbol"?h+"":h,C),C);function C(r){const o=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(o,t,i.get?i:{enumerable:!0,get:()=>r[t]})}}return o.default=r,Object.freeze(o)}const u=C(h);class M{static getBaseline(o){if(this.cache.has(o))return this.cache.get(o);const t=this.calculateBaselineFromDOM(o);return this.cache.set(o,t),t}static calculateBaselineFromDOM(o){if(typeof document>"u")return 0;const t=document.createElement("div"),i=document.createElement("span"),e=document.createElement("img");t.style.visibility="hidden",t.style.position="absolute",t.style.top="0px",t.style.left="0px",t.style.font=o,t.style.margin="0",t.style.padding="0",t.style.border="none",i.style.margin="0",i.style.padding="0",i.style.border="none",i.style.lineHeight="normal",e.width=1,e.height=1,e.style.verticalAlign="baseline",i.appendChild(document.createTextNode("Hidden Text")),t.appendChild(i),t.appendChild(e),document.body.appendChild(t);const a=e.offsetTop-i.offsetTop;return document.body.removeChild(t),a}}O(M,"cache",new Map);class R extends u.MeshBasicMaterial{constructor(t,i,e,a,n=2){super({transparent:!0,side:u.FrontSide,color:16777215});O(this,"canvas");O(this,"ctx");O(this,"qualityFactor");if(this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),!this.ctx)throw new Error("[Mirage] Failed to create canvas context");this.qualityFactor=n,this.map=new u.CanvasTexture(this.canvas),this.map.colorSpace=u.LinearSRGBColorSpace,this.map.minFilter=u.LinearFilter,this.map.magFilter=u.LinearFilter,this.updateText(t,i,e,a)}wrapText(t,i){const e=t.split(`
2
+ `),a=[];return e.forEach(n=>{const c=n.match(/[^\s\-]+\-?|\-|\s+/g)||[];if(c.length===0){a.push("");return}let s=c[0];for(let l=1;l<c.length;l++){const d=c[l];this.ctx.measureText(s+d).width<=i+2?s+=d:(s&&a.push(s),s=d.trimStart())}s&&a.push(s)}),a}updateText(t,i,e,a,n){n!==void 0&&(this.qualityFactor=n);const s=(window.devicePixelRatio||1)*this.qualityFactor,l=e*s,d=a*s;this.canvas.width!==l||this.canvas.height!==d?(this.canvas.width=l,this.canvas.height=d):this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.setTransform(s,0,0,s,0,0),this.ctx.font=i.font,this.ctx.fillStyle=i.color,this.ctx.textBaseline="alphabetic",this.ctx.globalAlpha=1;const g=this.wrapText(t,e),y=i.lineHeight,m=M.getBaseline(i.font);g.forEach((p,T)=>{const B=T*y+m;let w=0;i.textAlign==="center"?w=e/2:i.textAlign==="right"&&(w=e),this.ctx.textAlign=i.textAlign,this.ctx.fillText(p,w,B)}),this.map&&(this.map.needsUpdate=!0)}dispose(){this.map&&this.map.dispose(),super.dispose()}}function x(r){return typeof r=="number"?r:parseFloat(r)||0}function S(r){if(!r||r==="transparent")return{color:new u.Color(16777215),alpha:0};const o=r.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(o){const t=parseInt(o[1],10),i=parseInt(o[2],10),e=parseInt(o[3],10),a=o[4]!==void 0?parseFloat(o[4]):1;return{color:new u.Color(`rgb(${t}, ${i}, ${e})`),alpha:a}}return{color:new u.Color(r),alpha:1}}function k(r){const o=[];let t="",i=0;for(let e=0;e<r.length;e++){const a=r[e];if(a==="(")i++;else if(a===")")i--;else if(a===","&&i===0){o.push(t.trim()),t="";continue}t+=a}return t&&o.push(t.trim()),o}function P(r){if(!r||typeof r!="string"||!r.includes("linear-gradient"))return null;const o=r.match(/linear-gradient\((.*)\)/);if(!o)return null;const t=o[1],i=k(t);let e=Math.PI,a=0;const n=i[0].trim();if(n.startsWith("to "))n==="to top"?e=0:n==="to right"?e=Math.PI/2:n==="to bottom"?e=Math.PI:n==="to left"?e=Math.PI*1.5:n==="to top right"||n==="to right top"?e=Math.PI/4:n==="to bottom right"||n==="to right bottom"?e=Math.PI*.75:n==="to bottom left"||n==="to left bottom"?e=Math.PI*1.25:(n==="to top left"||n==="to left top")&&(e=Math.PI*1.75),a=1;else if(n.endsWith("deg")||n.endsWith("rad")||n.endsWith("turn")){const s=parseFloat(n);n.endsWith("deg")?e=s*(Math.PI/180):n.endsWith("rad")?e=s:n.endsWith("turn")&&(e=s*Math.PI*2),a=1}const c=[];for(let s=a;s<i.length&&!(c.length>=8);s++){const l=i[s].trim(),d=l.lastIndexOf(" ");let g=l,y=null;if(d!==-1&&!l.endsWith(")")){const p=l.substring(d+1);(p.endsWith("%")||p.endsWith("px")||!isNaN(parseFloat(p)))&&(g=l.substring(0,d).trim(),y=p)}const m=S(g);c.push({color:m.color,alpha:m.alpha,rawStop:y,stop:0})}if(c.length>0){for(let l=0;l<c.length;l++)c[l].rawStop!==null&&(c[l].stop=parseFloat(c[l].rawStop)/100);c[0].rawStop===null&&(c[0].stop=0),c.length>1&&c[c.length-1].rawStop===null&&(c[c.length-1].stop=1);let s=0;for(let l=1;l<c.length;l++)if(c[l].rawStop!==null||l===c.length-1){const d=l-s;if(d>1){const g=c[s].stop,m=(c[l].stop-g)/d;for(let p=1;p<d;p++)c[s+p].stop=g+m*p}s=l}}return{angle:e,stops:c}}const F=`varying vec2 vUv;
3
+ varying vec4 vScreenPos;
4
+ void main() {
5
+ vUv = uv;
6
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
7
+ vScreenPos = gl_Position;
8
+ }`,W=`varying vec2 vUv;
9
+ uniform vec2 uSize;
10
+ uniform vec4 uBorderRadius;
11
+ uniform float uBorderWidth;
12
+ uniform vec4 uBgColor;
13
+ uniform vec4 uBorderColor;
14
+ uniform float uOpacity;
15
+
16
+ uniform int uGradientCount;
17
+ uniform float uGradientAngle;
18
+ uniform vec4 uGradientColors[8];
19
+ uniform float uGradientStops[8];
20
+
21
+ #INJECT_DECLARATIONS
22
+
23
+ vec3 linearToSrgb(vec3 linearColor) {
24
+ vec3 linearPart = 12.92 * linearColor;
25
+ vec3 curvePart = 1.055 * pow(linearColor, vec3(1.0 / 2.4)) - 0.055;
26
+ vec3 switchCondition = step(vec3(0.0031308), linearColor);
27
+ return mix(linearPart, curvePart, switchCondition);
28
+ }
29
+
30
+ vec3 srgbToLinear(vec3 srgbColor) {
31
+ vec3 linearPart = srgbColor / 12.92;
32
+ vec3 curvePart = pow((srgbColor + vec3(0.055)) / 1.055, vec3(2.4));
33
+ vec3 switchCondition = step(vec3(0.04045), srgbColor);
34
+ return mix(linearPart, curvePart, switchCondition);
35
+ }
36
+
37
+ vec4 blendSrcOverInLinear(vec4 front, vec4 back) {
38
+ front.rgb = srgbToLinear(front.rgb);
39
+ back.rgb = srgbToLinear(back.rgb);
40
+ float aOut = front.a + back.a * (1.0 - front.a);
41
+ float safeAlpha = max(aOut, 0.0001);
42
+ vec3 cOut = (front.rgb * front.a + back.rgb * back.a * (1.0 - front.a)) / safeAlpha;
43
+ return vec4(linearToSrgb(cOut), aOut);
44
+ }
45
+
46
+ vec4 blendSrcOver(vec4 front, vec4 back) {
47
+ float aOut = front.a + back.a * (1.0 - front.a);
48
+ float safeAlpha = max(aOut, 0.0001);
49
+ vec3 cOut = (front.rgb * front.a + back.rgb * back.a * (1.0 - front.a)) / safeAlpha;
50
+ return vec4(cOut, aOut);
51
+ }
52
+
53
+ float sdRoundedBox(vec2 p, vec2 b, float r) {
54
+ vec2 q = abs(p) - b + r;
55
+ return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
56
+ }
57
+
58
+ float sdVisualBox(vec2 p, vec2 b, float r) {
59
+ vec2 q = abs(p) - b + r;
60
+ float n = 2.01;
61
+ float d = pow(pow(max(q.x, 0.0), n) + pow(max(q.y, 0.0), n), 1.0/n) - r;
62
+ return min(max(q.x, q.y), 0.0) + d;
63
+ }
64
+
65
+ vec4 calculateGradientLayer(vec2 uv) {
66
+ if (uGradientCount < 2) return uGradientColors[0];
67
+
68
+ vec2 dir = vec2(sin(uGradientAngle), cos(uGradientAngle));
69
+ vec2 p = (uv - 0.5) * uSize;
70
+ float proj = dot(p, dir);
71
+ float L = abs(uSize.x * dir.x) + abs(uSize.y * dir.y);
72
+ float t = clamp((proj / L) + 0.5, 0.0, 1.0);
13
73
 
14
- uniform vec2 uSize;
15
- uniform vec4 uBorderRadius;
16
- uniform float uBorderWidth;
17
- uniform vec3 uBgColor;
18
- uniform vec3 uBorderColor;
19
- uniform float uOpacity;
20
- uniform float uBgOpacity;
21
- uniform float uBorderOpacity;
22
-
23
- #INJECT_DECLARATIONS
24
-
25
- float sdRoundedBox(vec2 p, vec2 b, float r) {
26
- vec2 q = abs(p) - b + r;
27
-
28
- return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
29
- }
30
-
31
- void main() {
32
- vec2 p = (vUv - 0.5) * uSize;
33
- vec2 halfSize = uSize * 0.5;
34
-
35
- #INJECT_UV_MODIFIER
74
+ vec4 color = uGradientColors[0];
75
+ for (int i = 1; i < 8; i++) {
76
+ if (i >= uGradientCount) break;
77
+ float prevStop = uGradientStops[i-1];
78
+ float currStop = uGradientStops[i];
36
79
 
37
- // color decision pipeline
38
- vec4 baseColor = vec4(uBgColor, uBgOpacity);
80
+ float factor = clamp((t - prevStop) / (currStop - prevStop + 0.00001), 0.0, 1.0);
81
+ color = mix(color, uGradientColors[i], factor);
82
+ }
83
+ // return vec4(linearToSrgb(color.rgb), color.a);
84
+ return vec4(color);
85
+ }
39
86
 
40
- #INJECT_BASE_COLOR
87
+ void main() {
88
+ vec2 p = (vUv - 0.5) * uSize;
89
+ vec2 halfSize = uSize * 0.5;
41
90
 
42
- // sdf shape pipeline
43
- vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
44
- float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
45
- float d = sdRoundedBox(p, halfSize, r);
91
+ #INJECT_UV_MODIFIER
46
92
 
47
- float aa = 1.0;
48
- float bgMask = 1.0 - smoothstep(0.0, aa, d);
93
+ // color decision pipeline
94
+ vec4 baseColor = vec4(uBgColor.rgb, uBgColor.a);
49
95
 
50
- float halfBorder = uBorderWidth * 0.5;
51
- float borderD = abs(d + halfBorder) - halfBorder;
52
- float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
53
- if (uBorderWidth <= 0.01) {
54
- borderAlpha = 0.0;
55
- }
96
+ if (uGradientCount > 0) {
97
+ vec4 gradColor = calculateGradientLayer(vUv);
98
+ baseColor = blendSrcOver(gradColor, baseColor);
99
+ }
56
100
 
57
- // final blending (border + background)
58
- float aFront = borderAlpha;
59
- float aBack = baseColor.a;
60
- float aOut = aFront + aBack * (1.0 - aFront);
101
+ #INJECT_BASE_COLOR
61
102
 
62
- float safeAlpha = max(aOut, 0.0001);
63
- vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
64
- vec4 finalColor = vec4(cOut, aOut);
103
+ // Hybrid SDF
104
+ vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
105
+ float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
106
+ r = min(r, min(halfSize.x, halfSize.y));
65
107
 
66
- // final color control (Tint, Noise)
67
- #INJECT_COLOR_MODIFIER
108
+ float d = sdRoundedBox(p, halfSize, r);
68
109
 
69
- float finalOpacity = finalColor.a * bgMask * uOpacity;
70
- if (finalOpacity < 0.001) discard;
110
+ float d_smooth = sdVisualBox(p, halfSize, r);
111
+ float d_visual = d_smooth / fwidth(d_smooth);
71
112
 
72
- gl_FragColor = vec4(finalColor.rgb, finalOpacity);
113
+ // rendering pipeline
114
+ float bgMask = 1.0 - smoothstep(-0.5, 0.5, d_visual);
73
115
 
74
- #include <colorspace_fragment>
116
+ float halfBorder = uBorderWidth * 0.5;
117
+ float borderD = abs(d + halfBorder) - halfBorder;
118
+ float borderAlpha = (1.0 - smoothstep(0.0, 1.0, borderD)) * uBorderColor.a;
119
+ if (uBorderWidth <= 0.01) {
120
+ borderAlpha = 0.0;
75
121
  }
76
- `;function g(e){if(!e||e==="transparent")return{color:new c.Color(16777215),alpha:0};const r=e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(r){const o=parseInt(r[1],10),i=parseInt(r[2],10),t=parseInt(r[3],10),n=r[4]!==void 0?parseFloat(r[4]):1;return{color:new c.Color(`rgb(${o}, ${i}, ${t})`),alpha:n}}return{color:new c.Color(e),alpha:1}}function O(e,r,o,i=null,t){const n=i!==null,a=n?`
77
- uniform sampler2D uTexture;
78
- varying vec4 vScreenPos;
79
- `:"",l=n?`
80
- vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
81
- vec2 resultUv = screenUv;
82
- ${(t==null?void 0:t.uvModifier)||""}
83
- `:"",u=n?`
84
- baseColor = texture2D(uTexture, resultUv);
85
- `:"",d=(t==null?void 0:t.colorModifier)||"",x=w.replace("#INJECT_DECLARATIONS",a).replace("#INJECT_UV_MODIFIER",l).replace("#INJECT_BASE_COLOR",u).replace("#INJECT_COLOR_MODIFIER",d),f=g(e.backgroundColor),b=g(e.borderColor),B={uSize:{value:new c.Vector2(r,o)},uBorderRadius:{value:new c.Vector4(0,0,0,0)},uBorderWidth:{value:p(e.borderWidth)},uBgColor:{value:f.color},uBorderColor:{value:b.color},uOpacity:{value:e.opacity??1},uBgOpacity:{value:f.alpha},uBorderOpacity:{value:b.alpha},uTexture:{value:null}};return h(B.uBorderRadius.value,e.borderRadius),n&&(B.uTexture.value=i),new c.ShaderMaterial({uniforms:B,vertexShader:M,fragmentShader:x,transparent:!0,side:c.FrontSide})}function T(e,r,o,i,t){const n=g(r.backgroundColor),a=g(r.borderColor);e.uniforms.uSize.value.set(o,i),h(e.uniforms.uBorderRadius.value,r.borderRadius),e.uniforms.uBorderWidth.value=p(r.borderWidth),e.uniforms.uBgColor.value.copy(n.color),e.uniforms.uBorderColor.value.copy(a.color),e.uniforms.uOpacity.value=r.opacity??1,e.uniforms.uBgOpacity.value=n.alpha,e.uniforms.uBorderOpacity.value=a.alpha,e.uniforms.uTexture&&t!==void 0&&(e.uniforms.uTexture.value=t)}const y={create(e,r,o,i,t,n=2,a=null,l){if(e==="BOX")return O(r,i,t,a,l);if(e==="TEXT"){const u=m(o||"",r,i,t,n);return new c.MeshBasicMaterial({map:u,transparent:!0,side:c.FrontSide,color:16777215})}return new c.MeshBasicMaterial({visible:!1})},update(e,r,o,i,t,n,a=2,l){if(r==="BOX")T(e,o,t,n,l);else if(r==="TEXT"){const u=e;u.map&&u.map.dispose();const d=m(i||"",o,t,n,a);u.map=d,u.needsUpdate=!0}}};s.Painter=y,s.createBoxMaterial=O,s.createTextTexture=m,s.updateBoxMaterial=T,Object.defineProperty(s,Symbol.toStringTag,{value:"Module"})});
122
+
123
+ // final blending (border + background) using blendSrcOver
124
+ vec4 borderLayer = vec4(uBorderColor.rgb, borderAlpha);
125
+ vec4 finalColor = blendSrcOver(borderLayer, baseColor);
126
+ // final color control (Tint, Noise)
127
+ #INJECT_COLOR_MODIFIER
128
+
129
+ float finalOpacity = finalColor.a * bgMask * uOpacity;
130
+ if (finalOpacity < 0.001) discard;
131
+
132
+ gl_FragColor = vec4(finalColor.rgb, finalOpacity);
133
+
134
+
135
+ // #include <colorspace_fragment>
136
+ }
137
+ `,U=`uniform sampler2D uTexture;
138
+ varying vec4 vScreenPos;
139
+ uniform vec2 uTextureRepeat;
140
+ uniform vec2 uTextureOffset;`,V=`vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
141
+ vec2 resultUv = screenUv;
142
+
143
+ `,z=`vec4 texColor = texture2D(uTexture, resultUv);
144
+ baseColor = blendSrcOver(baseColor, texColor);`,G={vertexShader:F,fragmentShader:W},A={declChunk:U,uvChunk:V,baseColorChunk:z};function E(r,o,t,i=null,e){const a=i!==null||!!r.imageSrc;let n="";const c={};if(e!=null&&e.uniforms)for(const[b,v]of Object.entries(e.uniforms))typeof v=="number"?(n+=`uniform float ${b};
145
+ `,c[b]={value:v}):Array.isArray(v)?v.length===2?(n+=`uniform vec2 ${b};
146
+ `,c[b]={value:new u.Vector2(...v)}):v.length===3?(n+=`uniform vec3 ${b};
147
+ `,c[b]={value:new u.Vector3(...v)}):v.length===4&&(n+=`uniform vec4 ${b};
148
+ `,c[b]={value:new u.Vector4(...v)}):(n+=`uniform float ${b};
149
+ `,c[b]={value:v});const s=(a?A.declChunk:"")+`
150
+ `+n,l=r.isTraveler?A.uvChunk:`vec2 resultUv = vUv * uTextureRepeat + uTextureOffset;
151
+ `,d=a?l+((e==null?void 0:e.uvModifier)||""):"",g=a?A.baseColorChunk:"",y=(e==null?void 0:e.colorModifier)||"",m=G.fragmentShader.replace("#INJECT_DECLARATIONS",s).replace("#INJECT_UV_MODIFIER",d).replace("#INJECT_BASE_COLOR",g).replace("#INJECT_COLOR_MODIFIER",y),p=S(r.backgroundColor),T=S(r.borderColor),B={uSize:{value:new u.Vector2(o,t)},uBgColor:{value:new u.Vector4(p.color.r,p.color.g,p.color.b,p.alpha)},uBorderColor:{value:new u.Vector4(T.color.r,T.color.g,T.color.b,T.alpha)},uBorderRadius:{value:new u.Vector4(0,0,0,0)},uBorderWidth:{value:x(r.borderWidth)},uOpacity:{value:r.opacity??1},uTexture:{value:null},uTextureRepeat:{value:new u.Vector2(1,1)},uTextureOffset:{value:new u.Vector2(0,0)},uGradientCount:{value:0},uGradientAngle:{value:0},uGradientColors:{value:Array.from({length:8},()=>new u.Vector4(0,0,0,0))},uGradientStops:{value:new Float32Array(8)}};_(B.uBorderRadius.value,r.borderRadius),a&&(B.uTexture.value=i);const w=new u.ShaderMaterial({uniforms:{...B,...c},vertexShader:G.vertexShader,fragmentShader:m,transparent:!0,side:u.FrontSide});return r.backgroundImage&&I(w,{backgroundImage:r.backgroundImage}),w}function L(r,o,t,i,e){const a=x(o.borderWidth);I(r,{width:t,height:i,borderRadius:o.borderRadius,borderWidth:a,backgroundColor:o.backgroundColor,borderColor:o.borderColor,opacity:o.opacity,texture:e,backgroundImage:o.backgroundImage})}function I(r,o){var i;if(o.width!==void 0&&o.height!==void 0&&r.uniforms.uSize.value.set(o.width,o.height),o.borderRadius!==void 0&&_(r.uniforms.uBorderRadius.value,o.borderRadius),o.borderWidth!==void 0&&(r.uniforms.uBorderWidth.value=o.borderWidth),o.backgroundColor!==void 0)if(Array.isArray(o.backgroundColor)){const e=r.uniforms.uBgColor.value.w;r.uniforms.uBgColor.value.set(o.backgroundColor[0],o.backgroundColor[1],o.backgroundColor[2],e)}else if(typeof o.backgroundColor=="string"){const e=S(o.backgroundColor);r.uniforms.uBgColor.value.set(e.color.r,e.color.g,e.color.b,e.alpha)}else{const e=r.uniforms.uBgColor.value.w;r.uniforms.uBgColor.value.set(o.backgroundColor.r,o.backgroundColor.g,o.backgroundColor.b,e)}if(o.borderColor!==void 0)if(Array.isArray(o.borderColor)){const e=r.uniforms.uBorderColor.value.w;r.uniforms.uBorderColor.value.set(o.borderColor[0],o.borderColor[1],o.borderColor[2],e)}else if(typeof o.borderColor=="string"){const e=S(o.borderColor);r.uniforms.uBorderColor.value.set(e.color.r,e.color.g,e.color.b,e.alpha)}else{const e=r.uniforms.uBorderColor.value.w;r.uniforms.uBorderColor.value.set(o.borderColor.r,o.borderColor.g,o.borderColor.b,e)}o.opacity!==void 0&&(r.uniforms.uOpacity.value=o.opacity),o.bgOpacity!==void 0&&(r.uniforms.uBgColor.value.w=o.bgOpacity),o.borderOpacity!==void 0&&(r.uniforms.uBorderColor.value.w=o.borderOpacity),r.uniforms.uTexture&&o.texture!==void 0&&(r.uniforms.uTexture.value=o.texture);const t=o.texture!==void 0?o.texture:(i=r.uniforms.uTexture)==null?void 0:i.value;if(t&&(t.image instanceof ImageBitmap||t.image instanceof HTMLImageElement||t.image instanceof HTMLCanvasElement)){const e=t.image.width,a=t.image.height,n=o.width??r.uniforms.uSize.value.x,c=o.height??r.uniforms.uSize.value.y;if(e&&a&&n&&c){const s=e/a,l=n/c;if(s>l){const d=l/s;r.uniforms.uTextureRepeat.value.set(d,1),r.uniforms.uTextureOffset.value.set((1-d)/2,0)}else{const d=s/l;r.uniforms.uTextureRepeat.value.set(1,d),r.uniforms.uTextureOffset.value.set(0,(1-d)/2)}}}else r.uniforms.uTextureRepeat&&(r.uniforms.uTextureRepeat.value.set(1,1),r.uniforms.uTextureOffset.value.set(0,0));if(o.backgroundImage!==void 0){const e=P(o.backgroundImage);if(e){r.uniforms.uGradientCount.value=e.stops.length,r.uniforms.uGradientAngle.value=e.angle;for(let a=0;a<8;a++)if(a<e.stops.length){const n=e.stops[a];r.uniforms.uGradientColors.value[a].set(n.color.r,n.color.g,n.color.b,n.alpha),r.uniforms.uGradientStops.value[a]=n.stop}else r.uniforms.uGradientColors.value[a].set(0,0,0,0),r.uniforms.uGradientStops.value[a]=1}else r.uniforms.uGradientCount.value=0}for(const e of Object.keys(o))e!=="width"&&e!=="height"&&e!=="borderRadius"&&e!=="borderWidth"&&e!=="backgroundColor"&&e!=="borderColor"&&e!=="opacity"&&e!=="bgOpacity"&&e!=="borderOpacity"&&e!=="texture"&&e!=="backgroundImage"&&r.uniforms[e]!==void 0&&(r.uniforms[e].value!==void 0&&r.uniforms[e].value!==null&&typeof r.uniforms[e].value.set=="function"?Array.isArray(o[e])?r.uniforms[e].value.set(...o[e]):o[e]!==void 0&&(o[e].copy?r.uniforms[e].value.copy(o[e]):r.uniforms[e].value=o[e]):r.uniforms[e].value=o[e])}function _(r,o){if(o==null){r.set(0,0,0,0);return}if(typeof o=="number"){r.set(o,o,o,o);return}if(Array.isArray(o)){r.set(o[0],o[1],o[2],o[3]);return}const t=o.split("/")[0].trim().split(/\s+/),i=x(t[0]),e=x(t[1]??t[0]),a=x(t[2]??t[0]),n=x(t[3]??t[1]??t[0]);r.set(i,e,a,n)}const D={create(r,o,t,i,e,a=2,n=null,c){return r==="BOX"?E(o,i,e,n,c):r==="TEXT"?new R(t||"",o,i,e,a):new u.MeshBasicMaterial({visible:!1})},update(r,o,t,i,e,a,n=2,c){o==="BOX"?L(r,t,e,a,c):o==="TEXT"&&r.updateText(i||"",t,e,a,n)},forceUpdateUniforms(r,o){I(r,o)}};f.Painter=D,f.TextGenerator=R,f.createBoxMaterial=E,f.parseColor=S,f.parseLinearGradient=P,f.parsePixelValue=x,f.setBoxUniforms=I,f.splitByComma=k,f.updateBoxMaterial=L,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})});
@@ -0,0 +1,19 @@
1
+ import { BoxStyles, ShaderHooks } from '../types';
2
+ import * as THREE from "three";
3
+ export declare function createBoxMaterial(styles: BoxStyles, width: number, height: number, texture?: THREE.Texture | null, hooks?: ShaderHooks): THREE.ShaderMaterial;
4
+ export declare function updateBoxMaterial(material: THREE.ShaderMaterial, styles: BoxStyles, width: number, height: number, texture?: THREE.Texture | null): void;
5
+ export interface BoxUniformValues {
6
+ width?: number;
7
+ height?: number;
8
+ borderRadius?: string | number | [number, number, number, number];
9
+ borderWidth?: number;
10
+ backgroundColor?: THREE.Color | [number, number, number] | string;
11
+ borderColor?: THREE.Color | [number, number, number] | string;
12
+ opacity?: number;
13
+ bgOpacity?: number;
14
+ borderOpacity?: number;
15
+ texture?: THREE.Texture | null;
16
+ backgroundImage?: string;
17
+ [key: string]: any;
18
+ }
19
+ export declare function setBoxUniforms(material: THREE.ShaderMaterial, values: BoxUniformValues): void;
@@ -1,6 +1,8 @@
1
- import { ShaderHooks } from './types';
1
+ import { BoxStyles, TextStyles, ShaderHooks } from './types';
2
+ import { BoxUniformValues } from './Box/BoxGenerator';
2
3
  import * as THREE from "three";
3
4
  export declare const Painter: {
4
- create(type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number, texture?: THREE.Texture | null, shaderHooks?: ShaderHooks): THREE.Material;
5
- update(material: THREE.Material, type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number, texture?: THREE.Texture | null): void;
5
+ create(type: "BOX" | "TEXT", styles: BoxStyles | TextStyles, content: string, width: number, height: number, quality?: number, texture?: THREE.Texture | null, shaderHooks?: ShaderHooks): THREE.Material;
6
+ update(material: THREE.Material, type: "BOX" | "TEXT", styles: BoxStyles | TextStyles, content: string, width: number, height: number, quality?: number, texture?: THREE.Texture | null): void;
7
+ forceUpdateUniforms(material: THREE.ShaderMaterial, values: BoxUniformValues): void;
6
8
  };
@@ -0,0 +1,10 @@
1
+ export declare class FontMetricsManager {
2
+ private static cache;
3
+ /**
4
+ * Calculates or retrieves the exact alphabetic baseline (in pixels) for the given font.
5
+ * @param font CSS font shorthand string (e.g., "400 16px Inter")
6
+ * @returns Baseline Y offset in pixels
7
+ */
8
+ static getBaseline(font: string): number;
9
+ private static calculateBaselineFromDOM;
10
+ }
@@ -0,0 +1,11 @@
1
+ import { TextStyles } from '../types';
2
+ import * as THREE from "three";
3
+ export declare class TextGenerator extends THREE.MeshBasicMaterial {
4
+ private canvas;
5
+ private ctx;
6
+ private qualityFactor;
7
+ constructor(text: string, styles: TextStyles, rectWidth: number, rectHeight: number, qualityFactor?: number);
8
+ private wrapText;
9
+ updateText(text: string, styles: TextStyles, rectWidth: number, rectHeight: number, qualityFactor?: number): void;
10
+ dispose(): void;
11
+ }
@@ -1,4 +1,5 @@
1
- export * from './TextGenerator';
1
+ export * from './Text/TextGenerator';
2
2
  export * from './types';
3
- export * from './BoxGenerator';
3
+ export * from './Box/BoxGenerator';
4
4
  export * from './Painter';
5
+ export * from './tools/parser';
@@ -0,0 +1,9 @@
1
+ export declare const BoxShader: {
2
+ vertexShader: string;
3
+ fragmentShader: string;
4
+ };
5
+ export declare const BoxChunk: {
6
+ declChunk: string;
7
+ uvChunk: string;
8
+ baseColorChunk: string;
9
+ };
@@ -0,0 +1,16 @@
1
+ import * as THREE from "three";
2
+ export declare function parsePixelValue(value: string | number): number;
3
+ export declare function parseColor(colorStr: string): {
4
+ color: THREE.Color;
5
+ alpha: number;
6
+ };
7
+ export declare function splitByComma(str: string): string[];
8
+ export declare function parseLinearGradient(value: string | null | undefined): {
9
+ angle: number;
10
+ stops: {
11
+ color: THREE.Color;
12
+ alpha: number;
13
+ rawStop: string | null;
14
+ stop: number;
15
+ }[];
16
+ } | null;
@@ -1,5 +1,6 @@
1
1
  export interface TextStyles {
2
2
  font: string;
3
+ fontSize: string;
3
4
  color: string;
4
5
  textAlign: CanvasTextAlign;
5
6
  textBaseline: CanvasTextBaseline;
@@ -9,6 +10,9 @@ export interface TextStyles {
9
10
  }
10
11
  export interface BoxStyles {
11
12
  backgroundColor: string;
13
+ backgroundImage: string;
14
+ imageSrc?: string;
15
+ isTraveler?: boolean;
12
16
  opacity: number;
13
17
  zIndex: number;
14
18
  borderRadius: string;
@@ -18,4 +22,5 @@ export interface BoxStyles {
18
22
  export interface ShaderHooks {
19
23
  uvModifier?: string;
20
24
  colorModifier?: string;
25
+ uniforms?: Record<string, any>;
21
26
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirage-engine/painter",
3
- "version": "0.4.0",
3
+ "version": "1.0.1",
4
4
  "main": "src/index.ts",
5
5
  "types": "src/index.ts",
6
6
  "peerDependencies": {