@mirage-engine/painter 0.3.0 → 1.0.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 +12 -0
- package/dist/mirage-painter.js +478 -192
- package/dist/mirage-painter.umd.js +127 -67
- package/dist/src/Box/BoxGenerator.d.ts +18 -0
- package/dist/src/Painter.d.ts +5 -2
- package/dist/src/Text/FontMetricsManager.d.ts +10 -0
- package/dist/src/Text/TextGenerator.d.ts +11 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/shaders/index.d.ts +9 -0
- package/dist/src/tools/parser.d.ts +16 -0
- package/dist/src/types/common.d.ts +8 -0
- package/package.json +1 -1
- package/src/Box/BoxGenerator.ts +286 -0
- package/src/Painter.ts +30 -26
- package/src/Text/FontMetricsManager.ts +62 -0
- package/src/Text/TextGenerator.ts +123 -0
- package/src/env.d.ts +4 -0
- package/src/index.ts +4 -3
- package/src/shaders/base/box-fragment.glsl +129 -0
- package/src/shaders/base/box-vertex.glsl +7 -0
- package/src/shaders/chunk/base-color-chunk.glsl +2 -0
- package/src/shaders/chunk/decl-chunk.glsl +4 -0
- package/src/shaders/chunk/uv-chunk.glsl +3 -0
- package/src/shaders/index.ts +16 -0
- package/src/tools/parser.ts +148 -0
- package/src/types/common.ts +10 -1
- package/dist/src/BoxGenerator.d.ts +0 -7
- package/dist/src/TextGenerator.d.ts +0 -3
- package/dist/src/dev/devShader.d.ts +0 -0
- package/src/BoxGenerator.ts +0 -217
- package/src/TextGenerator.ts +0 -85
- package/src/dev/devShader.ts +0 -0
package/src/BoxGenerator.ts
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
import { BoxStyles } from "./types";
|
|
3
|
-
|
|
4
|
-
function parsePixelValue(value: string | number): number {
|
|
5
|
-
if (typeof value === "number") return value;
|
|
6
|
-
return parseFloat(value) || 0;
|
|
7
|
-
}
|
|
8
|
-
|
|
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 */ `
|
|
24
|
-
varying vec2 vUv;
|
|
25
|
-
varying vec4 vScreenPos;
|
|
26
|
-
void main() {
|
|
27
|
-
vUv = uv;
|
|
28
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
29
|
-
vScreenPos = gl_Position;
|
|
30
|
-
}
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
const fragmentShaderTemplate = /* glsl */ `
|
|
34
|
-
|
|
35
|
-
varying vec2 vUv;
|
|
36
|
-
|
|
37
|
-
uniform vec2 uSize;
|
|
38
|
-
uniform vec4 uBorderRadius;
|
|
39
|
-
uniform float uBorderWidth;
|
|
40
|
-
uniform vec3 uBgColor;
|
|
41
|
-
uniform vec3 uBorderColor;
|
|
42
|
-
uniform float uOpacity;
|
|
43
|
-
uniform float uBgOpacity;
|
|
44
|
-
uniform float uBorderOpacity;
|
|
45
|
-
|
|
46
|
-
#INJECT_DECLARATIONS
|
|
47
|
-
|
|
48
|
-
float sdRoundedBox(vec2 p, vec2 b, float r) {
|
|
49
|
-
vec2 q = abs(p) - b + r;
|
|
50
|
-
|
|
51
|
-
return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
void main() {
|
|
55
|
-
vec2 p = (vUv - 0.5) * uSize;
|
|
56
|
-
vec2 halfSize = uSize * 0.5;
|
|
57
|
-
|
|
58
|
-
#INJECT_UV_MODIFIER
|
|
59
|
-
|
|
60
|
-
// color decision pipeline
|
|
61
|
-
vec4 baseColor = vec4(uBgColor, uBgOpacity);
|
|
62
|
-
|
|
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;
|
|
78
|
-
}
|
|
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;
|
|
93
|
-
if (finalOpacity < 0.001) discard;
|
|
94
|
-
|
|
95
|
-
gl_FragColor = vec4(finalColor.rgb, finalOpacity);
|
|
96
|
-
|
|
97
|
-
#include <colorspace_fragment>
|
|
98
|
-
}
|
|
99
|
-
`;
|
|
100
|
-
|
|
101
|
-
function parseColor(colorStr: string) {
|
|
102
|
-
if (!colorStr || colorStr === "transparent") {
|
|
103
|
-
return { color: new THREE.Color(0xffffff), alpha: 0.0 };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const rgbaMatch = colorStr.match(
|
|
107
|
-
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/,
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
if (rgbaMatch) {
|
|
111
|
-
const r = parseInt(rgbaMatch[1], 10);
|
|
112
|
-
const g = parseInt(rgbaMatch[2], 10);
|
|
113
|
-
const b = parseInt(rgbaMatch[3], 10);
|
|
114
|
-
const a = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1.0;
|
|
115
|
-
|
|
116
|
-
return { color: new THREE.Color(`rgb(${r}, ${g}, ${b})`), alpha: a };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return { color: new THREE.Color(colorStr), alpha: 1.0 };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function createBoxMaterial(
|
|
123
|
-
styles: BoxStyles,
|
|
124
|
-
width: number,
|
|
125
|
-
height: number,
|
|
126
|
-
texture: THREE.Texture | null = null,
|
|
127
|
-
hooks?: { uvModifier?: string; colorModifier?: string },
|
|
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
|
|
161
|
-
const parsedBg = parseColor(styles.backgroundColor);
|
|
162
|
-
const parsedBorder = parseColor(styles.borderColor);
|
|
163
|
-
const uniforms = {
|
|
164
|
-
uSize: { value: new THREE.Vector2(width, height) },
|
|
165
|
-
uBorderRadius: { value: new THREE.Vector4(0, 0, 0, 0) },
|
|
166
|
-
uBorderWidth: { value: parsePixelValue(styles.borderWidth) },
|
|
167
|
-
uBgColor: { value: parsedBg.color },
|
|
168
|
-
uBorderColor: { value: parsedBorder.color },
|
|
169
|
-
uOpacity: { value: styles.opacity ?? 1.0 },
|
|
170
|
-
uBgOpacity: { value: parsedBg.alpha },
|
|
171
|
-
uBorderOpacity: { value: parsedBorder.alpha },
|
|
172
|
-
uTexture: { value: null as THREE.Texture | null },
|
|
173
|
-
};
|
|
174
|
-
// border radius value initialize
|
|
175
|
-
setBorderRadius(uniforms.uBorderRadius.value, styles.borderRadius);
|
|
176
|
-
|
|
177
|
-
if (hasTexture) {
|
|
178
|
-
uniforms.uTexture.value = texture;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const material = new THREE.ShaderMaterial({
|
|
182
|
-
uniforms: uniforms,
|
|
183
|
-
vertexShader: vertexShader,
|
|
184
|
-
fragmentShader: fragmentShader,
|
|
185
|
-
transparent: true,
|
|
186
|
-
side: THREE.FrontSide, // for better performance
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
return material;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export function updateBoxMaterial(
|
|
193
|
-
material: THREE.ShaderMaterial,
|
|
194
|
-
styles: BoxStyles,
|
|
195
|
-
width: number,
|
|
196
|
-
height: number,
|
|
197
|
-
texture?: THREE.Texture | null
|
|
198
|
-
) {
|
|
199
|
-
const parsedBg = parseColor(styles.backgroundColor);
|
|
200
|
-
const parsedBorder = parseColor(styles.borderColor);
|
|
201
|
-
|
|
202
|
-
material.uniforms.uSize.value.set(width, height);
|
|
203
|
-
|
|
204
|
-
// material.uniforms.uBorderRadius.value = parsePixelValue(styles.borderRadius);
|
|
205
|
-
setBorderRadius(material.uniforms.uBorderRadius.value, styles.borderRadius);
|
|
206
|
-
material.uniforms.uBorderWidth.value = parsePixelValue(styles.borderWidth);
|
|
207
|
-
|
|
208
|
-
material.uniforms.uBgColor.value.copy(parsedBg.color);
|
|
209
|
-
material.uniforms.uBorderColor.value.copy(parsedBorder.color);
|
|
210
|
-
|
|
211
|
-
material.uniforms.uOpacity.value = styles.opacity ?? 1.0;
|
|
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
|
-
}
|
|
217
|
-
}
|
package/src/TextGenerator.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import * as THREE from "three";
|
|
2
|
-
import { TextStyles } from "./types";
|
|
3
|
-
|
|
4
|
-
function wrapText(
|
|
5
|
-
ctx: CanvasRenderingContext2D,
|
|
6
|
-
text: string,
|
|
7
|
-
maxWidth: number
|
|
8
|
-
): string[] {
|
|
9
|
-
const forcedLines = text.split("\n");
|
|
10
|
-
const resultLines: string[] = [];
|
|
11
|
-
|
|
12
|
-
forcedLines.forEach((line) => {
|
|
13
|
-
const words = line.split(" ");
|
|
14
|
-
let currentLine = words[0];
|
|
15
|
-
|
|
16
|
-
for (let i = 1; i < words.length; i++) {
|
|
17
|
-
const word = words[i];
|
|
18
|
-
const width = ctx.measureText(currentLine + " " + word).width;
|
|
19
|
-
|
|
20
|
-
if (width < maxWidth) {
|
|
21
|
-
currentLine += " " + word;
|
|
22
|
-
} else {
|
|
23
|
-
resultLines.push(currentLine);
|
|
24
|
-
currentLine = word;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
resultLines.push(currentLine);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return resultLines;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function createTextTexture(
|
|
34
|
-
text: string,
|
|
35
|
-
styles: TextStyles,
|
|
36
|
-
rectWidth: number,
|
|
37
|
-
rectHeight: number,
|
|
38
|
-
qualityFactor: number = 2
|
|
39
|
-
): THREE.CanvasTexture {
|
|
40
|
-
const canvas = document.createElement("canvas");
|
|
41
|
-
const ctx = canvas.getContext("2d");
|
|
42
|
-
|
|
43
|
-
if (!ctx) {
|
|
44
|
-
throw new Error("[Mirage] Failed to create canvas context");
|
|
45
|
-
}
|
|
46
|
-
// text Quality
|
|
47
|
-
|
|
48
|
-
const pixelRatio = window.devicePixelRatio || 1;
|
|
49
|
-
const scale = pixelRatio * qualityFactor;
|
|
50
|
-
|
|
51
|
-
canvas.width = rectWidth * scale;
|
|
52
|
-
canvas.height = rectHeight * scale;
|
|
53
|
-
ctx.scale(scale, scale);
|
|
54
|
-
|
|
55
|
-
ctx.font = styles.font;
|
|
56
|
-
ctx.fillStyle = styles.color;
|
|
57
|
-
ctx.textBaseline = "top";
|
|
58
|
-
|
|
59
|
-
ctx.globalAlpha = 1;
|
|
60
|
-
|
|
61
|
-
const lines = wrapText(ctx, text, rectWidth);
|
|
62
|
-
const lineHeight = styles.lineHeight;
|
|
63
|
-
|
|
64
|
-
lines.forEach((line, index) => {
|
|
65
|
-
const y = index * lineHeight + 2;
|
|
66
|
-
|
|
67
|
-
let x = 0;
|
|
68
|
-
if (styles.textAlign === "center") {
|
|
69
|
-
x = rectWidth / 2;
|
|
70
|
-
} else if (styles.textAlign === "right") {
|
|
71
|
-
x = rectWidth;
|
|
72
|
-
}
|
|
73
|
-
ctx.textAlign = styles.textAlign as CanvasTextAlign;
|
|
74
|
-
|
|
75
|
-
ctx.fillText(line, x, y);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const texture = new THREE.CanvasTexture(canvas);
|
|
79
|
-
texture.colorSpace = THREE.SRGBColorSpace;
|
|
80
|
-
texture.minFilter = THREE.LinearFilter;
|
|
81
|
-
texture.magFilter = THREE.LinearFilter;
|
|
82
|
-
texture.needsUpdate = true;
|
|
83
|
-
|
|
84
|
-
return texture;
|
|
85
|
-
}
|
package/src/dev/devShader.ts
DELETED
|
File without changes
|