@mirage-engine/painter 0.2.3 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @mirage-engine/painter
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e4efe5c: travel api
8
+
3
9
  ## 0.2.3
4
10
 
5
11
  ### Patch Changes
@@ -1,138 +1,193 @@
1
1
  import * as l from "three";
2
- function b(o, e, t) {
3
- const n = e.split(`
4
- `), r = [];
5
- return n.forEach((i) => {
6
- const a = i.split(" ");
7
- let u = a[0];
8
- for (let c = 1; c < a.length; c++) {
9
- const d = a[c];
10
- o.measureText(u + " " + d).width < t ? u += " " + d : (r.push(u), u = d);
2
+ function h(e, r, o) {
3
+ const i = r.split(`
4
+ `), a = [];
5
+ return i.forEach((n) => {
6
+ const t = n.split(" ");
7
+ let c = t[0];
8
+ for (let u = 1; u < t.length; u++) {
9
+ const s = t[u];
10
+ e.measureText(c + " " + s).width < o ? c += " " + s : (a.push(c), c = s);
11
11
  }
12
- r.push(u);
13
- }), r;
12
+ a.push(c);
13
+ }), a;
14
14
  }
15
- function m(o, e, t, n, r = 2) {
16
- const i = document.createElement("canvas"), a = i.getContext("2d");
17
- if (!a)
15
+ function m(e, r, o, i, a = 2) {
16
+ const n = document.createElement("canvas"), t = n.getContext("2d");
17
+ if (!t)
18
18
  throw new Error("[Mirage] Failed to create canvas context");
19
- const c = (window.devicePixelRatio || 1) * r;
20
- i.width = t * c, i.height = n * c, a.scale(c, c), a.font = e.font, a.fillStyle = e.color, a.textBaseline = "top", a.globalAlpha = 1;
21
- const d = b(a, o, t), v = e.lineHeight;
22
- d.forEach((x, g) => {
23
- const B = g * v + 2;
24
- let h = 0;
25
- e.textAlign === "center" ? h = t / 2 : e.textAlign === "right" && (h = t), a.textAlign = e.textAlign, a.fillText(x, h, B);
19
+ const u = (window.devicePixelRatio || 1) * a;
20
+ n.width = o * u, n.height = i * u, t.scale(u, u), t.font = r.font, t.fillStyle = r.color, t.textBaseline = "top", t.globalAlpha = 1;
21
+ const s = h(t, e, o), v = r.lineHeight;
22
+ s.forEach((B, p) => {
23
+ const g = p * v + 2;
24
+ let C = 0;
25
+ r.textAlign === "center" ? C = o / 2 : r.textAlign === "right" && (C = o), t.textAlign = r.textAlign, t.fillText(B, C, g);
26
26
  });
27
- const s = new l.CanvasTexture(i);
28
- return s.colorSpace = l.SRGBColorSpace, s.minFilter = l.LinearFilter, s.magFilter = l.LinearFilter, s.needsUpdate = !0, s;
27
+ const d = new l.CanvasTexture(n);
28
+ return d.colorSpace = l.SRGBColorSpace, d.minFilter = l.LinearFilter, d.magFilter = l.LinearFilter, d.needsUpdate = !0, d;
29
29
  }
30
- function p(o) {
31
- return typeof o == "number" ? o : parseFloat(o) || 0;
30
+ function f(e) {
31
+ return typeof e == "number" ? e : parseFloat(e) || 0;
32
32
  }
33
- const C = `
33
+ function b(e, r) {
34
+ if (!r) {
35
+ e.set(0, 0, 0, 0);
36
+ return;
37
+ }
38
+ const o = r.split("/")[0].trim().split(/\s+/), i = f(o[0]), a = f(o[1] ?? o[0]), n = f(o[2] ?? o[0]), t = f(o[3] ?? o[1] ?? o[0]);
39
+ e.set(i, a, n, t);
40
+ }
41
+ const O = (
42
+ /* glsl */
43
+ `
34
44
  varying vec2 vUv;
45
+ varying vec4 vScreenPos;
35
46
  void main() {
36
47
  vUv = uv;
37
48
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
49
+ vScreenPos = gl_Position;
38
50
  }
39
- `, w = `
51
+ `
52
+ ), T = (
53
+ /* glsl */
54
+ `
55
+
40
56
  varying vec2 vUv;
41
57
 
42
58
  uniform vec2 uSize;
43
- uniform float uRadius;
59
+ uniform vec4 uBorderRadius;
44
60
  uniform float uBorderWidth;
45
- uniform vec3 uColor;
61
+ uniform vec3 uBgColor;
46
62
  uniform vec3 uBorderColor;
47
63
  uniform float uOpacity;
48
64
  uniform float uBgOpacity;
65
+ uniform float uBorderOpacity;
66
+
67
+ #INJECT_DECLARATIONS
49
68
 
50
- // SDF 박스 함수
51
69
  float sdRoundedBox(vec2 p, vec2 b, float r) {
52
70
  vec2 q = abs(p) - b + r;
71
+
53
72
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
54
73
  }
55
74
 
56
75
  void main() {
57
76
  vec2 p = (vUv - 0.5) * uSize;
58
77
  vec2 halfSize = uSize * 0.5;
59
-
60
- float d = sdRoundedBox(p, halfSize, uRadius);
61
-
62
- float smoothEdge = 1.0;
63
78
 
64
- float fillAlpha = 1.0 - smoothstep(-uBorderWidth - smoothEdge, -uBorderWidth, d);
79
+ #INJECT_UV_MODIFIER
65
80
 
66
- float borderAlpha = 0.0;
67
-
68
- if (uBorderWidth > 0.01) {
69
- borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
70
- }
81
+ // color decision pipeline
82
+ vec4 baseColor = vec4(uBgColor, uBgOpacity);
71
83
 
72
- vec3 color = uColor;
73
- float totalAlpha = borderAlpha + fillAlpha;
74
-
75
- if (totalAlpha > 0.001) {
76
- color = mix(uColor, uBorderColor, borderAlpha / totalAlpha);
84
+ #INJECT_BASE_COLOR
85
+
86
+ // sdf shape pipeline
87
+ vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
88
+ float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
89
+ float d = sdRoundedBox(p, halfSize, r);
90
+
91
+ float aa = 1.0;
92
+ float bgMask = 1.0 - smoothstep(0.0, aa, d);
93
+
94
+ float halfBorder = uBorderWidth * 0.5;
95
+ float borderD = abs(d + halfBorder) - halfBorder;
96
+ float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
97
+ if (uBorderWidth <= 0.01) {
98
+ borderAlpha = 0.0;
77
99
  }
78
-
79
- float shapeAlpha = borderAlpha + (fillAlpha * uBgOpacity);
80
- float finalOpacity = shapeAlpha * uOpacity;
81
-
100
+
101
+ // final blending (border + background)
102
+ float aFront = borderAlpha;
103
+ float aBack = baseColor.a;
104
+ float aOut = aFront + aBack * (1.0 - aFront);
105
+
106
+ float safeAlpha = max(aOut, 0.0001);
107
+ vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
108
+ vec4 finalColor = vec4(cOut, aOut);
109
+
110
+ // final color control (Tint, Noise)
111
+ #INJECT_COLOR_MODIFIER
112
+
113
+ float finalOpacity = finalColor.a * bgMask * uOpacity;
82
114
  if (finalOpacity < 0.001) discard;
83
115
 
84
- gl_FragColor = vec4(color, finalOpacity);
116
+ gl_FragColor = vec4(finalColor.rgb, finalOpacity);
117
+
118
+ #include <colorspace_fragment>
85
119
  }
86
- `;
87
- function f(o) {
88
- if (!o || o === "transparent")
120
+ `
121
+ );
122
+ function x(e) {
123
+ if (!e || e === "transparent")
89
124
  return { color: new l.Color(16777215), alpha: 0 };
90
- const e = o.match(
125
+ const r = e.match(
91
126
  /rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/
92
127
  );
93
- if (e) {
94
- const t = parseInt(e[1], 10), n = parseInt(e[2], 10), r = parseInt(e[3], 10), i = e[4] !== void 0 ? parseFloat(e[4]) : 1;
95
- return { color: new l.Color(`rgb(${t}, ${n}, ${r})`), alpha: i };
128
+ if (r) {
129
+ const o = parseInt(r[1], 10), i = parseInt(r[2], 10), a = parseInt(r[3], 10), n = r[4] !== void 0 ? parseFloat(r[4]) : 1;
130
+ return { color: new l.Color(`rgb(${o}, ${i}, ${a})`), alpha: n };
96
131
  }
97
- return { color: new l.Color(o), alpha: 1 };
132
+ return { color: new l.Color(e), alpha: 1 };
98
133
  }
99
- function A(o, e, t) {
100
- const n = f(o.backgroundColor), r = f(o.borderColor), i = {
101
- uSize: { value: new l.Vector2(e, t) },
102
- uRadius: { value: p(o.borderRadius) },
103
- uBorderWidth: { value: p(o.borderWidth) },
104
- uColor: { value: n.color },
105
- uBorderColor: { value: r.color },
106
- uOpacity: { value: o.opacity ?? 1 },
107
- uBgOpacity: { value: n.alpha }
134
+ function R(e, r, o, i = null, a) {
135
+ const n = i !== null, t = n ? (
136
+ /* glsl */
137
+ `
138
+ uniform sampler2D uTexture;
139
+ varying vec4 vScreenPos;
140
+ `
141
+ ) : "", c = n ? (
142
+ /* glsl */
143
+ `
144
+ vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
145
+ vec2 resultUv = screenUv;
146
+ ${(a == null ? void 0 : a.uvModifier) || ""}
147
+ `
148
+ ) : "", u = n ? (
149
+ /* glsl */
150
+ `
151
+ baseColor = texture2D(uTexture, resultUv);
152
+ `
153
+ ) : "", s = (a == null ? void 0 : a.colorModifier) || "", v = T.replace("#INJECT_DECLARATIONS", t).replace("#INJECT_UV_MODIFIER", c).replace("#INJECT_BASE_COLOR", u).replace("#INJECT_COLOR_MODIFIER", s), d = x(e.backgroundColor), B = x(e.borderColor), p = {
154
+ uSize: { value: new l.Vector2(r, o) },
155
+ uBorderRadius: { value: new l.Vector4(0, 0, 0, 0) },
156
+ uBorderWidth: { value: f(e.borderWidth) },
157
+ uBgColor: { value: d.color },
158
+ uBorderColor: { value: B.color },
159
+ uOpacity: { value: e.opacity ?? 1 },
160
+ uBgOpacity: { value: d.alpha },
161
+ uBorderOpacity: { value: B.alpha },
162
+ uTexture: { value: null }
108
163
  };
109
- return new l.ShaderMaterial({
110
- uniforms: i,
111
- vertexShader: C,
112
- fragmentShader: w,
164
+ return b(p.uBorderRadius.value, e.borderRadius), n && (p.uTexture.value = i), new l.ShaderMaterial({
165
+ uniforms: p,
166
+ vertexShader: O,
167
+ fragmentShader: v,
113
168
  transparent: !0,
114
169
  side: l.FrontSide
115
170
  // for better performance
116
171
  });
117
172
  }
118
- function S(o, e, t, n) {
119
- const r = f(e.backgroundColor), i = f(e.borderColor);
120
- o.uniforms.uSize.value.set(t, n), o.uniforms.uRadius.value = p(e.borderRadius), o.uniforms.uBorderWidth.value = p(e.borderWidth), o.uniforms.uColor.value.copy(r.color), o.uniforms.uBorderColor.value.copy(i.color), o.uniforms.uOpacity.value = e.opacity ?? 1, o.uniforms.uBgOpacity.value = r.alpha;
173
+ function E(e, r, o, i, a) {
174
+ const n = x(r.backgroundColor), t = x(r.borderColor);
175
+ e.uniforms.uSize.value.set(o, i), b(e.uniforms.uBorderRadius.value, r.borderRadius), e.uniforms.uBorderWidth.value = f(r.borderWidth), e.uniforms.uBgColor.value.copy(n.color), e.uniforms.uBorderColor.value.copy(t.color), e.uniforms.uOpacity.value = r.opacity ?? 1, e.uniforms.uBgOpacity.value = n.alpha, e.uniforms.uBorderOpacity.value = t.alpha, e.uniforms.uTexture && a !== void 0 && (e.uniforms.uTexture.value = a);
121
176
  }
122
- const O = {
123
- create(o, e, t, n, r, i = 2) {
124
- if (o === "BOX")
125
- return A(e, n, r);
126
- if (o === "TEXT") {
127
- const a = m(
128
- t || "",
129
- e,
130
- n,
177
+ const S = {
178
+ create(e, r, o, i, a, n = 2, t = null) {
179
+ if (e === "BOX")
180
+ return R(r, i, a, t);
181
+ if (e === "TEXT") {
182
+ const c = m(
183
+ o || "",
131
184
  r,
132
- i
185
+ i,
186
+ a,
187
+ n
133
188
  );
134
189
  return new l.MeshBasicMaterial({
135
- map: a,
190
+ map: c,
136
191
  transparent: !0,
137
192
  side: l.FrontSide,
138
193
  color: 16777215
@@ -140,31 +195,32 @@ const O = {
140
195
  }
141
196
  return new l.MeshBasicMaterial({ visible: !1 });
142
197
  },
143
- update(o, e, t, n, r, i, a = 2) {
144
- if (e === "BOX")
145
- S(
198
+ update(e, r, o, i, a, n, t = 2, c) {
199
+ if (r === "BOX")
200
+ E(
201
+ e,
146
202
  o,
147
- t,
148
- r,
149
- i
203
+ a,
204
+ n,
205
+ c
150
206
  );
151
- else if (e === "TEXT") {
152
- const u = o;
207
+ else if (r === "TEXT") {
208
+ const u = e;
153
209
  u.map && u.map.dispose();
154
- const c = m(
155
- n || "",
156
- t,
157
- r,
158
- i,
159
- a
210
+ const s = m(
211
+ i || "",
212
+ o,
213
+ a,
214
+ n,
215
+ t
160
216
  );
161
- u.map = c, u.needsUpdate = !0;
217
+ u.map = s, u.needsUpdate = !0;
162
218
  }
163
219
  }
164
220
  };
165
221
  export {
166
- O as Painter,
167
- A as createBoxMaterial,
222
+ S as Painter,
223
+ R as createBoxMaterial,
168
224
  m as createTextTexture,
169
- S as updateBoxMaterial
225
+ E as updateBoxMaterial
170
226
  };
@@ -1,55 +1,85 @@
1
- (function(c,s){typeof exports=="object"&&typeof module<"u"?s(exports,require("three")):typeof define=="function"&&define.amd?define(["exports","three"],s):(c=typeof globalThis<"u"?globalThis:c||self,s(c.MiragePainter={},c.THREE))})(this,function(c,s){"use strict";function C(e){const o=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const r in e)if(r!=="default"){const t=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(o,r,t.get?t:{enumerable:!0,get:()=>e[r]})}}return o.default=e,Object.freeze(o)}const l=C(s);function w(e,o,r){const t=o.split(`
2
- `),a=[];return t.forEach(i=>{const n=i.split(" ");let u=n[0];for(let d=1;d<n.length;d++){const f=n[d];e.measureText(u+" "+f).width<r?u+=" "+f:(a.push(u),u=f)}a.push(u)}),a}function m(e,o,r,t,a=2){const i=document.createElement("canvas"),n=i.getContext("2d");if(!n)throw new Error("[Mirage] Failed to create canvas context");const d=(window.devicePixelRatio||1)*a;i.width=r*d,i.height=t*d,n.scale(d,d),n.font=o.font,n.fillStyle=o.color,n.textBaseline="top",n.globalAlpha=1;const f=w(n,e,r),B=o.lineHeight;f.forEach((S,M)=>{const R=M*B+2;let g=0;o.textAlign==="center"?g=r/2:o.textAlign==="right"&&(g=r),n.textAlign=o.textAlign,n.fillText(S,g,R)});const p=new l.CanvasTexture(i);return p.colorSpace=l.SRGBColorSpace,p.minFilter=l.LinearFilter,p.magFilter=l.LinearFilter,p.needsUpdate=!0,p}function h(e){return typeof e=="number"?e:parseFloat(e)||0}const A=`
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
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
- `,O=`
10
+ `,w=`
11
+
9
12
  varying vec2 vUv;
10
13
 
11
14
  uniform vec2 uSize;
12
- uniform float uRadius;
15
+ uniform vec4 uBorderRadius;
13
16
  uniform float uBorderWidth;
14
- uniform vec3 uColor;
17
+ uniform vec3 uBgColor;
15
18
  uniform vec3 uBorderColor;
16
19
  uniform float uOpacity;
17
20
  uniform float uBgOpacity;
21
+ uniform float uBorderOpacity;
22
+
23
+ #INJECT_DECLARATIONS
18
24
 
19
- // SDF 박스 함수
20
25
  float sdRoundedBox(vec2 p, vec2 b, float r) {
21
26
  vec2 q = abs(p) - b + r;
27
+
22
28
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
23
29
  }
24
30
 
25
31
  void main() {
26
32
  vec2 p = (vUv - 0.5) * uSize;
27
33
  vec2 halfSize = uSize * 0.5;
28
-
29
- float d = sdRoundedBox(p, halfSize, uRadius);
30
-
31
- float smoothEdge = 1.0;
32
34
 
33
- float fillAlpha = 1.0 - smoothstep(-uBorderWidth - smoothEdge, -uBorderWidth, d);
35
+ #INJECT_UV_MODIFIER
34
36
 
35
- float borderAlpha = 0.0;
36
-
37
- if (uBorderWidth > 0.01) {
38
- borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
39
- }
37
+ // color decision pipeline
38
+ vec4 baseColor = vec4(uBgColor, uBgOpacity);
40
39
 
41
- vec3 color = uColor;
42
- float totalAlpha = borderAlpha + fillAlpha;
43
-
44
- if (totalAlpha > 0.001) {
45
- color = mix(uColor, uBorderColor, borderAlpha / totalAlpha);
40
+ #INJECT_BASE_COLOR
41
+
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);
46
+
47
+ float aa = 1.0;
48
+ float bgMask = 1.0 - smoothstep(0.0, aa, d);
49
+
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;
46
55
  }
47
-
48
- float shapeAlpha = borderAlpha + (fillAlpha * uBgOpacity);
49
- float finalOpacity = shapeAlpha * uOpacity;
50
-
56
+
57
+ // final blending (border + background)
58
+ float aFront = borderAlpha;
59
+ float aBack = baseColor.a;
60
+ float aOut = aFront + aBack * (1.0 - aFront);
61
+
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);
65
+
66
+ // final color control (Tint, Noise)
67
+ #INJECT_COLOR_MODIFIER
68
+
69
+ float finalOpacity = finalColor.a * bgMask * uOpacity;
51
70
  if (finalOpacity < 0.001) discard;
52
71
 
53
- gl_FragColor = vec4(color, finalOpacity);
72
+ gl_FragColor = vec4(finalColor.rgb, finalOpacity);
73
+
74
+ #include <colorspace_fragment>
54
75
  }
55
- `;function v(e){if(!e||e==="transparent")return{color:new l.Color(16777215),alpha:0};const o=e.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);if(o){const r=parseInt(o[1],10),t=parseInt(o[2],10),a=parseInt(o[3],10),i=o[4]!==void 0?parseFloat(o[4]):1;return{color:new l.Color(`rgb(${r}, ${t}, ${a})`),alpha:i}}return{color:new l.Color(e),alpha:1}}function x(e,o,r){const t=v(e.backgroundColor),a=v(e.borderColor),i={uSize:{value:new l.Vector2(o,r)},uRadius:{value:h(e.borderRadius)},uBorderWidth:{value:h(e.borderWidth)},uColor:{value:t.color},uBorderColor:{value:a.color},uOpacity:{value:e.opacity??1},uBgOpacity:{value:t.alpha}};return new l.ShaderMaterial({uniforms:i,vertexShader:A,fragmentShader:O,transparent:!0,side:l.FrontSide})}function b(e,o,r,t){const a=v(o.backgroundColor),i=v(o.borderColor);e.uniforms.uSize.value.set(r,t),e.uniforms.uRadius.value=h(o.borderRadius),e.uniforms.uBorderWidth.value=h(o.borderWidth),e.uniforms.uColor.value.copy(a.color),e.uniforms.uBorderColor.value.copy(i.color),e.uniforms.uOpacity.value=o.opacity??1,e.uniforms.uBgOpacity.value=a.alpha}const T={create(e,o,r,t,a,i=2){if(e==="BOX")return x(o,t,a);if(e==="TEXT"){const n=m(r||"",o,t,a,i);return new l.MeshBasicMaterial({map:n,transparent:!0,side:l.FrontSide,color:16777215})}return new l.MeshBasicMaterial({visible:!1})},update(e,o,r,t,a,i,n=2){if(o==="BOX")b(e,r,a,i);else if(o==="TEXT"){const u=e;u.map&&u.map.dispose();const d=m(t||"",r,a,i,n);u.map=d,u.needsUpdate=!0}}};c.Painter=T,c.createBoxMaterial=x,c.createTextTexture=m,c.updateBoxMaterial=b,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});
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){if(e==="BOX")return O(r,i,t,a);if(e==="TEXT"){const l=m(o||"",r,i,t,n);return new c.MeshBasicMaterial({map:l,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"})});
@@ -1,4 +1,7 @@
1
1
  import { BoxStyles } from './types';
2
2
  import * as THREE from "three";
3
- export declare function createBoxMaterial(styles: BoxStyles, width: number, height: number): THREE.ShaderMaterial;
4
- export declare function updateBoxMaterial(material: THREE.ShaderMaterial, styles: BoxStyles, width: number, height: number): void;
3
+ export declare function createBoxMaterial(styles: BoxStyles, width: number, height: number, texture?: THREE.Texture | null, hooks?: {
4
+ uvModifier?: string;
5
+ colorModifier?: string;
6
+ }): THREE.ShaderMaterial;
7
+ export declare function updateBoxMaterial(material: THREE.ShaderMaterial, styles: BoxStyles, width: number, height: number, texture?: THREE.Texture | null): void;
@@ -1,5 +1,5 @@
1
1
  import * as THREE from "three";
2
2
  export declare const Painter: {
3
- create(type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number): THREE.Material;
4
- update(material: THREE.Material, type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number): void;
3
+ create(type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number, texture?: THREE.Texture | null): THREE.Material;
4
+ update(material: THREE.Material, type: "BOX" | "TEXT", styles: any, content: string, width: number, height: number, quality?: number, texture?: THREE.Texture | null): void;
5
5
  };
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mirage-engine/painter",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "main": "src/index.ts",
5
5
  "types": "src/index.ts",
6
6
  "peerDependencies": {
@@ -6,60 +6,95 @@ function parsePixelValue(value: string | number): number {
6
6
  return parseFloat(value) || 0;
7
7
  }
8
8
 
9
- const vertexShader = `
9
+ function setBorderRadius(target: THREE.Vector4, radius: string) {
10
+ if (!radius) {
11
+ target.set(0, 0, 0, 0);
12
+ return;
13
+ }
14
+ const arr = radius.split("/")[0].trim().split(/\s+/);
15
+ const tl = parsePixelValue(arr[0]);
16
+ const tr = parsePixelValue(arr[1] ?? arr[0]);
17
+ const br = parsePixelValue(arr[2] ?? arr[0]);
18
+ const bl = parsePixelValue(arr[3] ?? arr[1] ?? arr[0]);
19
+
20
+ target.set(tl, tr, br, bl);
21
+ }
22
+
23
+ const vertexShader = /* glsl */ `
10
24
  varying vec2 vUv;
25
+ varying vec4 vScreenPos;
11
26
  void main() {
12
27
  vUv = uv;
13
28
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
29
+ vScreenPos = gl_Position;
14
30
  }
15
31
  `;
16
32
 
17
- const fragmentShader = `
33
+ const fragmentShaderTemplate = /* glsl */ `
34
+
18
35
  varying vec2 vUv;
19
36
 
20
37
  uniform vec2 uSize;
21
- uniform float uRadius;
38
+ uniform vec4 uBorderRadius;
22
39
  uniform float uBorderWidth;
23
- uniform vec3 uColor;
40
+ uniform vec3 uBgColor;
24
41
  uniform vec3 uBorderColor;
25
42
  uniform float uOpacity;
26
43
  uniform float uBgOpacity;
44
+ uniform float uBorderOpacity;
45
+
46
+ #INJECT_DECLARATIONS
27
47
 
28
- // SDF 박스 함수
29
48
  float sdRoundedBox(vec2 p, vec2 b, float r) {
30
49
  vec2 q = abs(p) - b + r;
50
+
31
51
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
32
52
  }
33
53
 
34
54
  void main() {
35
55
  vec2 p = (vUv - 0.5) * uSize;
36
56
  vec2 halfSize = uSize * 0.5;
37
-
38
- float d = sdRoundedBox(p, halfSize, uRadius);
39
-
40
- float smoothEdge = 1.0;
41
57
 
42
- float fillAlpha = 1.0 - smoothstep(-uBorderWidth - smoothEdge, -uBorderWidth, d);
43
-
44
- float borderAlpha = 0.0;
58
+ #INJECT_UV_MODIFIER
45
59
 
46
- if (uBorderWidth > 0.01) {
47
- borderAlpha = (1.0 - smoothstep(0.0, smoothEdge, d)) - fillAlpha;
48
- }
60
+ // color decision pipeline
61
+ vec4 baseColor = vec4(uBgColor, uBgOpacity);
49
62
 
50
- vec3 color = uColor;
51
- float totalAlpha = borderAlpha + fillAlpha;
52
-
53
- if (totalAlpha > 0.001) {
54
- color = mix(uColor, uBorderColor, borderAlpha / totalAlpha);
63
+ #INJECT_BASE_COLOR
64
+
65
+ // sdf shape pipeline
66
+ vec2 xRadii = mix(uBorderRadius.xw, uBorderRadius.yz, step(0.0, p.x));
67
+ float r = mix(xRadii.y, xRadii.x, step(0.0, p.y));
68
+ float d = sdRoundedBox(p, halfSize, r);
69
+
70
+ float aa = 1.0;
71
+ float bgMask = 1.0 - smoothstep(0.0, aa, d);
72
+
73
+ float halfBorder = uBorderWidth * 0.5;
74
+ float borderD = abs(d + halfBorder) - halfBorder;
75
+ float borderAlpha = (1.0 - smoothstep(0.0, aa, borderD)) * uBorderOpacity;
76
+ if (uBorderWidth <= 0.01) {
77
+ borderAlpha = 0.0;
55
78
  }
56
-
57
- float shapeAlpha = borderAlpha + (fillAlpha * uBgOpacity);
58
- float finalOpacity = shapeAlpha * uOpacity;
59
-
79
+
80
+ // final blending (border + background)
81
+ float aFront = borderAlpha;
82
+ float aBack = baseColor.a;
83
+ float aOut = aFront + aBack * (1.0 - aFront);
84
+
85
+ float safeAlpha = max(aOut, 0.0001);
86
+ vec3 cOut = (uBorderColor * aFront + baseColor.rgb * aBack * (1.0 - aFront)) / safeAlpha;
87
+ vec4 finalColor = vec4(cOut, aOut);
88
+
89
+ // final color control (Tint, Noise)
90
+ #INJECT_COLOR_MODIFIER
91
+
92
+ float finalOpacity = finalColor.a * bgMask * uOpacity;
60
93
  if (finalOpacity < 0.001) discard;
61
94
 
62
- gl_FragColor = vec4(color, finalOpacity);
95
+ gl_FragColor = vec4(finalColor.rgb, finalOpacity);
96
+
97
+ #include <colorspace_fragment>
63
98
  }
64
99
  `;
65
100
 
@@ -88,19 +123,60 @@ export function createBoxMaterial(
88
123
  styles: BoxStyles,
89
124
  width: number,
90
125
  height: number,
126
+ texture: THREE.Texture | null = null,
127
+ hooks?: { uvModifier?: string; colorModifier?: string },
91
128
  ): THREE.ShaderMaterial {
129
+ const hasTexture = texture !== null;
130
+
131
+ const declChunk = hasTexture
132
+ ? /* glsl */ `
133
+ uniform sampler2D uTexture;
134
+ varying vec4 vScreenPos;
135
+ `
136
+ : "";
137
+
138
+ const uvChunk = hasTexture
139
+ ? /* glsl */ `
140
+ vec2 screenUv = (vScreenPos.xy / vScreenPos.w) * 0.5 + 0.5;
141
+ vec2 resultUv = screenUv;
142
+ ${hooks?.uvModifier || ""}
143
+ `
144
+ : "";
145
+
146
+ const baseColorChunk = hasTexture
147
+ ? /* glsl */ `
148
+ baseColor = texture2D(uTexture, resultUv);
149
+ `
150
+ : "";
151
+
152
+ const colorModChunk = hooks?.colorModifier || "";
153
+
154
+ const fragmentShader = fragmentShaderTemplate
155
+ .replace("#INJECT_DECLARATIONS", declChunk)
156
+ .replace("#INJECT_UV_MODIFIER", uvChunk)
157
+ .replace("#INJECT_BASE_COLOR", baseColorChunk)
158
+ .replace("#INJECT_COLOR_MODIFIER", colorModChunk);
159
+
160
+ // uniform setting
92
161
  const parsedBg = parseColor(styles.backgroundColor);
93
162
  const parsedBorder = parseColor(styles.borderColor);
94
-
95
163
  const uniforms = {
96
164
  uSize: { value: new THREE.Vector2(width, height) },
97
- uRadius: { value: parsePixelValue(styles.borderRadius) },
165
+ uBorderRadius: { value: new THREE.Vector4(0, 0, 0, 0) },
98
166
  uBorderWidth: { value: parsePixelValue(styles.borderWidth) },
99
- uColor: { value: parsedBg.color },
167
+ uBgColor: { value: parsedBg.color },
100
168
  uBorderColor: { value: parsedBorder.color },
101
169
  uOpacity: { value: styles.opacity ?? 1.0 },
102
170
  uBgOpacity: { value: parsedBg.alpha },
171
+ uBorderOpacity: { value: parsedBorder.alpha },
172
+ uTexture: { value: null as THREE.Texture | null },
103
173
  };
174
+ // border radius value initialize
175
+ setBorderRadius(uniforms.uBorderRadius.value, styles.borderRadius);
176
+
177
+ if (hasTexture) {
178
+ uniforms.uTexture.value = texture;
179
+ }
104
180
 
105
181
  const material = new THREE.ShaderMaterial({
106
182
  uniforms: uniforms,
@@ -118,18 +194,24 @@ export function updateBoxMaterial(
118
194
  styles: BoxStyles,
119
195
  width: number,
120
196
  height: number,
197
+ texture?: THREE.Texture | null
121
198
  ) {
122
199
  const parsedBg = parseColor(styles.backgroundColor);
123
200
  const parsedBorder = parseColor(styles.borderColor);
124
201
 
125
202
  material.uniforms.uSize.value.set(width, height);
126
203
 
127
- material.uniforms.uRadius.value = parsePixelValue(styles.borderRadius);
204
+ // material.uniforms.uBorderRadius.value = parsePixelValue(styles.borderRadius);
205
+ setBorderRadius(material.uniforms.uBorderRadius.value, styles.borderRadius);
128
206
  material.uniforms.uBorderWidth.value = parsePixelValue(styles.borderWidth);
129
207
 
130
- material.uniforms.uColor.value.copy(parsedBg.color);
208
+ material.uniforms.uBgColor.value.copy(parsedBg.color);
131
209
  material.uniforms.uBorderColor.value.copy(parsedBorder.color);
132
210
 
133
211
  material.uniforms.uOpacity.value = styles.opacity ?? 1.0;
134
212
  material.uniforms.uBgOpacity.value = parsedBg.alpha;
213
+ material.uniforms.uBorderOpacity.value = parsedBorder.alpha;
214
+ if (material.uniforms.uTexture && texture !== undefined) {
215
+ material.uniforms.uTexture.value = texture;
216
+ }
135
217
  }
package/src/Painter.ts CHANGED
@@ -11,9 +11,10 @@ export const Painter = {
11
11
  width: number,
12
12
  height: number,
13
13
  quality: number = 2,
14
+ texture: THREE.Texture | null = null,
14
15
  ): THREE.Material {
15
16
  if (type === "BOX") {
16
- return createBoxMaterial(styles as BoxStyles, width, height);
17
+ return createBoxMaterial(styles as BoxStyles, width, height, texture);
17
18
  } else if (type === "TEXT") {
18
19
  const texture = createTextTexture(
19
20
  content || "",
@@ -42,6 +43,7 @@ export const Painter = {
42
43
  width: number,
43
44
  height: number,
44
45
  quality: number = 2,
46
+ texture?: THREE.Texture | null
45
47
  ) {
46
48
  if (type === "BOX") {
47
49
  updateBoxMaterial(
@@ -49,6 +51,7 @@ export const Painter = {
49
51
  styles as BoxStyles,
50
52
  width,
51
53
  height,
54
+ texture
52
55
  );
53
56
  } else if (type === "TEXT") {
54
57
  const basicMat = material as THREE.MeshBasicMaterial;
File without changes