@number10/phaserjsx 4.0.0 → 4.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.
- package/README.md +30 -0
- package/dist/chunk-C2EiDwsr.cjs +35 -0
- package/dist/clip/index.cjs +696 -0
- package/dist/clip/index.cjs.map +1 -0
- package/dist/clip/index.d.ts +3 -0
- package/dist/clip/index.d.ts.map +1 -0
- package/dist/clip/index.js +688 -0
- package/dist/clip/index.js.map +1 -0
- package/dist/clip/stencil-clip-extension.d.ts +18 -0
- package/dist/clip/stencil-clip-extension.d.ts.map +1 -0
- package/dist/clip/stencil-clip.d.ts +55 -7
- package/dist/clip/stencil-clip.d.ts.map +1 -1
- package/dist/components/appliers/applyBackground.d.ts +2 -1
- package/dist/components/appliers/applyBackground.d.ts.map +1 -1
- package/dist/components/appliers/applyTooltip.d.ts.map +1 -1
- package/dist/components/backgroundImage.d.ts +12 -0
- package/dist/components/backgroundImage.d.ts.map +1 -0
- package/dist/components/creators/createBackground.d.ts +2 -1
- package/dist/components/creators/createBackground.d.ts.map +1 -1
- package/dist/components/custom/Accordion.d.ts.map +1 -1
- package/dist/components/custom/Dropdown.d.ts.map +1 -1
- package/dist/components/custom/index.cjs +1 -1
- package/dist/components/custom/index.js +1 -1
- package/dist/components/primitives/view.d.ts.map +1 -1
- package/dist/{custom-oy3mBnrW.js → custom-C_w8D39m.js} +178 -481
- package/dist/custom-C_w8D39m.js.map +1 -0
- package/dist/{custom-BN31OAJq.cjs → custom-Dp3yAJdU.cjs} +187 -515
- package/dist/custom-Dp3yAJdU.cjs.map +1 -0
- package/dist/hooks.d.ts +9 -8
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.cjs +103 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -22
- package/dist/index.js.map +1 -1
- package/dist/layout/appliers/background-applier.d.ts.map +1 -1
- package/dist/layout/layout-engine.d.ts.map +1 -1
- package/dist/layout/types.d.ts +2 -1
- package/dist/layout/types.d.ts.map +1 -1
- package/dist/scene-backgrounds.d.ts +51 -1
- package/dist/scene-backgrounds.d.ts.map +1 -1
- package/package.json +6 -1
- package/dist/custom-BN31OAJq.cjs.map +0 -1
- package/dist/custom-oy3mBnrW.js.map +0 -1
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_chunk = require("../chunk-C2EiDwsr.cjs");
|
|
3
|
+
let phaser = require("phaser");
|
|
4
|
+
phaser = require_chunk.__toESM(phaser, 1);
|
|
5
|
+
//#region src/clip/stencil-clip.ts
|
|
6
|
+
/**
|
|
7
|
+
* WebGL stencil-buffer clip for Phaser 4 Containers.
|
|
8
|
+
*
|
|
9
|
+
* Supports arbitrary nesting via the INCR/DECR model: each clip level
|
|
10
|
+
* increments the stencil on enter and decrements on exit, so child clips are
|
|
11
|
+
* automatically intersected with their parent clips at the hardware level.
|
|
12
|
+
*
|
|
13
|
+
* Shape variants:
|
|
14
|
+
* - Plain rectangle (cornerRadius omitted or 0)
|
|
15
|
+
* - Rounded rectangle (uniform radius or per-corner object)
|
|
16
|
+
*
|
|
17
|
+
* A single SDF-based shader handles both variants. For plain rectangles
|
|
18
|
+
* u_radii is vec4(0) and the `discard` branch never fires — no overhead
|
|
19
|
+
* compared to a rectangle-only shader.
|
|
20
|
+
*
|
|
21
|
+
* Transforms (translate, scale, rotation) are fully supported: the quad
|
|
22
|
+
* corners are transformed through `container.getWorldTransformMatrix()` at
|
|
23
|
+
* render time, so no per-layout world-position tracking is needed.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Shared depth counter per GL context.
|
|
27
|
+
* Incremented before each clip's INCR pass, decremented after the DECR pass.
|
|
28
|
+
* Reset to 0 at the start of every frame via a `prerender` hook.
|
|
29
|
+
*/
|
|
30
|
+
var _depthByGl = /* @__PURE__ */ new WeakMap();
|
|
31
|
+
function getDepth(gl) {
|
|
32
|
+
let d = _depthByGl.get(gl);
|
|
33
|
+
if (!d) {
|
|
34
|
+
d = { value: 0 };
|
|
35
|
+
_depthByGl.set(gl, d);
|
|
36
|
+
}
|
|
37
|
+
return d;
|
|
38
|
+
}
|
|
39
|
+
var _prerenderHooked = /* @__PURE__ */ new WeakSet();
|
|
40
|
+
/**
|
|
41
|
+
* Registers a per-frame `prerender` listener that resets the depth counter.
|
|
42
|
+
* Registered at most once per Phaser.Game instance.
|
|
43
|
+
*/
|
|
44
|
+
function ensurePrerenderReset(gl, game) {
|
|
45
|
+
if (_prerenderHooked.has(game)) return;
|
|
46
|
+
_prerenderHooked.add(game);
|
|
47
|
+
game.events.on("prerender", () => {
|
|
48
|
+
const d = _depthByGl.get(gl);
|
|
49
|
+
if (d) d.value = 0;
|
|
50
|
+
const fbo = _fboPatchByGl.get(gl);
|
|
51
|
+
if (fbo) {
|
|
52
|
+
fbo.current = null;
|
|
53
|
+
fbo.saved = null;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
var _fboPatchByGl = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
/**
|
|
59
|
+
* Patches `gl.bindFramebuffer` once per GL context so that the stencil test
|
|
60
|
+
* is automatically disabled when Phaser switches to an off-screen FBO (for
|
|
61
|
+
* PostFX / RenderTexture rendering) and restored when switching back.
|
|
62
|
+
*
|
|
63
|
+
* Without this, a PostFX child rendered inside a stencil-clipped container
|
|
64
|
+
* would be invisible: the FBO's stencil buffer starts at 0 while the active
|
|
65
|
+
* stencil test requires `EQUAL(myDepth + 1)`, causing every fragment to fail.
|
|
66
|
+
*
|
|
67
|
+
* The patch is installed once per GL context and remains active for the
|
|
68
|
+
* lifetime of the renderer. It is a no-op when no stencil clip is active.
|
|
69
|
+
*/
|
|
70
|
+
function ensureFboPatch(gl) {
|
|
71
|
+
if (_fboPatchByGl.has(gl)) return;
|
|
72
|
+
const state = {
|
|
73
|
+
current: null,
|
|
74
|
+
saved: null
|
|
75
|
+
};
|
|
76
|
+
_fboPatchByGl.set(gl, state);
|
|
77
|
+
const origBind = gl.bindFramebuffer.bind(gl);
|
|
78
|
+
gl.bindFramebuffer = (target, fb) => {
|
|
79
|
+
const wasMain = state.current === null;
|
|
80
|
+
const willBeMain = fb === null;
|
|
81
|
+
const enteringOffscreen = wasMain && !willBeMain;
|
|
82
|
+
const leavingOffscreen = !wasMain && willBeMain;
|
|
83
|
+
if (enteringOffscreen && gl.getParameter(gl.STENCIL_TEST)) {
|
|
84
|
+
state.saved = {
|
|
85
|
+
func: gl.getParameter(gl.STENCIL_FUNC),
|
|
86
|
+
ref: gl.getParameter(gl.STENCIL_REF),
|
|
87
|
+
valueMask: gl.getParameter(gl.STENCIL_VALUE_MASK),
|
|
88
|
+
fail: gl.getParameter(gl.STENCIL_FAIL),
|
|
89
|
+
zfail: gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL),
|
|
90
|
+
zpass: gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS),
|
|
91
|
+
writeMask: gl.getParameter(gl.STENCIL_WRITEMASK)
|
|
92
|
+
};
|
|
93
|
+
gl.disable(gl.STENCIL_TEST);
|
|
94
|
+
}
|
|
95
|
+
origBind(target, fb);
|
|
96
|
+
state.current = fb;
|
|
97
|
+
if (leavingOffscreen && state.saved) {
|
|
98
|
+
gl.enable(gl.STENCIL_TEST);
|
|
99
|
+
gl.stencilFunc(state.saved.func, state.saved.ref, state.saved.valueMask);
|
|
100
|
+
gl.stencilOp(state.saved.fail, state.saved.zfail, state.saved.zpass);
|
|
101
|
+
gl.stencilMask(state.saved.writeMask);
|
|
102
|
+
state.saved = null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Vertex attributes:
|
|
108
|
+
* a_ndc vec2 — NDC clip-space position (CPU-computed from world transform)
|
|
109
|
+
* a_loc vec2 — position relative to the clip rect's center (for SDF)
|
|
110
|
+
*
|
|
111
|
+
* All four corners are pre-transformed on the CPU so the vertex shader is a
|
|
112
|
+
* pure pass-through. This correctly handles translation, scale, and rotation
|
|
113
|
+
* without a matrix uniform.
|
|
114
|
+
*/
|
|
115
|
+
var ROUND_RECT_VERT_SRC = `
|
|
116
|
+
attribute vec2 a_ndc;
|
|
117
|
+
attribute vec2 a_loc;
|
|
118
|
+
varying vec2 v_loc;
|
|
119
|
+
void main(){gl_Position=vec4(a_ndc,0.,1.);v_loc=a_loc;}
|
|
120
|
+
`;
|
|
121
|
+
/**
|
|
122
|
+
* SDF rounded-rectangle fragment shader.
|
|
123
|
+
*
|
|
124
|
+
* sdRoundedBox uses the IQ per-corner-radius technique:
|
|
125
|
+
* r.xy = p.x > 0 ? r.yz : r.xw (right → tr/br, left → tl/bl)
|
|
126
|
+
* r.x = p.y > 0 ? r.y : r.x (bottom or top within that pair)
|
|
127
|
+
*
|
|
128
|
+
* u_radii layout: (tl, tr, br, bl).
|
|
129
|
+
*
|
|
130
|
+
* For plain rectangles u_radii = vec4(0.0) and sdRoundedBox returns ≤ 0 for
|
|
131
|
+
* all fragments inside the quad, so `discard` is never executed.
|
|
132
|
+
*/
|
|
133
|
+
var ROUND_RECT_FRAG_SRC = `
|
|
134
|
+
precision mediump float;
|
|
135
|
+
varying vec2 v_loc;
|
|
136
|
+
uniform vec2 u_halfSize;
|
|
137
|
+
uniform vec4 u_radii;
|
|
138
|
+
float sdRoundedBox(vec2 p,vec2 b,vec4 r){
|
|
139
|
+
r.xy=p.x>0.?r.yz:r.xw;
|
|
140
|
+
r.x =p.y>0.?r.y :r.x;
|
|
141
|
+
vec2 q=abs(p)-b+r.x;
|
|
142
|
+
return length(max(q,0.))+min(max(q.x,q.y),0.)-r.x;
|
|
143
|
+
}
|
|
144
|
+
void main(){
|
|
145
|
+
if(sdRoundedBox(v_loc,u_halfSize,u_radii)>0.)discard;
|
|
146
|
+
gl_FragColor=vec4(0.);
|
|
147
|
+
}
|
|
148
|
+
`;
|
|
149
|
+
var BITMAP_VERT_SRC = `
|
|
150
|
+
attribute vec2 a_ndc;
|
|
151
|
+
attribute vec2 a_uv;
|
|
152
|
+
varying vec2 v_uv;
|
|
153
|
+
void main(){gl_Position=vec4(a_ndc,0.,1.);v_uv=a_uv;}
|
|
154
|
+
`;
|
|
155
|
+
var BITMAP_FRAG_SRC = `
|
|
156
|
+
precision mediump float;
|
|
157
|
+
varying vec2 v_uv;
|
|
158
|
+
uniform sampler2D u_texture;
|
|
159
|
+
uniform float u_alphaThreshold;
|
|
160
|
+
uniform float u_invertAlpha;
|
|
161
|
+
void main(){
|
|
162
|
+
float a=texture2D(u_texture,v_uv).a;
|
|
163
|
+
bool keep=u_invertAlpha>0.5 ? a<u_alphaThreshold : a>=u_alphaThreshold;
|
|
164
|
+
if(!keep)discard;
|
|
165
|
+
gl_FragColor=vec4(0.);
|
|
166
|
+
}
|
|
167
|
+
`;
|
|
168
|
+
var _roundRectProgByGl = /* @__PURE__ */ new WeakMap();
|
|
169
|
+
var _bitmapProgByGl = /* @__PURE__ */ new WeakMap();
|
|
170
|
+
/**
|
|
171
|
+
* Returns (or lazily creates) the SDF stencil shader program for a GL context.
|
|
172
|
+
* @param gl - The WebGL context.
|
|
173
|
+
* @returns Compiled and linked WebGLProgram.
|
|
174
|
+
*/
|
|
175
|
+
function createProgram(gl, vertSrc, fragSrc) {
|
|
176
|
+
const vs = gl.createShader(gl.VERTEX_SHADER);
|
|
177
|
+
gl.shaderSource(vs, vertSrc);
|
|
178
|
+
gl.compileShader(vs);
|
|
179
|
+
const fs = gl.createShader(gl.FRAGMENT_SHADER);
|
|
180
|
+
gl.shaderSource(fs, fragSrc);
|
|
181
|
+
gl.compileShader(fs);
|
|
182
|
+
const prog = gl.createProgram();
|
|
183
|
+
gl.attachShader(prog, vs);
|
|
184
|
+
gl.attachShader(prog, fs);
|
|
185
|
+
gl.linkProgram(prog);
|
|
186
|
+
return prog;
|
|
187
|
+
}
|
|
188
|
+
function getRoundRectProg(gl) {
|
|
189
|
+
let prog = _roundRectProgByGl.get(gl);
|
|
190
|
+
if (prog) return prog;
|
|
191
|
+
prog = createProgram(gl, ROUND_RECT_VERT_SRC, ROUND_RECT_FRAG_SRC);
|
|
192
|
+
_roundRectProgByGl.set(gl, prog);
|
|
193
|
+
return prog;
|
|
194
|
+
}
|
|
195
|
+
function getBitmapProg(gl) {
|
|
196
|
+
let prog = _bitmapProgByGl.get(gl);
|
|
197
|
+
if (prog) return prog;
|
|
198
|
+
prog = createProgram(gl, BITMAP_VERT_SRC, BITMAP_FRAG_SRC);
|
|
199
|
+
_bitmapProgByGl.set(gl, prog);
|
|
200
|
+
return prog;
|
|
201
|
+
}
|
|
202
|
+
var _roundRectLocsByProg = /* @__PURE__ */ new WeakMap();
|
|
203
|
+
var _bitmapLocsByProg = /* @__PURE__ */ new WeakMap();
|
|
204
|
+
/**
|
|
205
|
+
* Returns (or resolves and caches) the attribute/uniform locations for a program.
|
|
206
|
+
* @param gl - The WebGL context.
|
|
207
|
+
* @param prog - The shader program.
|
|
208
|
+
* @returns Cached locations.
|
|
209
|
+
*/
|
|
210
|
+
function getRoundRectShaderLocs(gl, prog) {
|
|
211
|
+
let l = _roundRectLocsByProg.get(prog);
|
|
212
|
+
if (!l) {
|
|
213
|
+
l = {
|
|
214
|
+
ndc: gl.getAttribLocation(prog, "a_ndc"),
|
|
215
|
+
loc: gl.getAttribLocation(prog, "a_loc"),
|
|
216
|
+
halfSize: gl.getUniformLocation(prog, "u_halfSize"),
|
|
217
|
+
radii: gl.getUniformLocation(prog, "u_radii")
|
|
218
|
+
};
|
|
219
|
+
_roundRectLocsByProg.set(prog, l);
|
|
220
|
+
}
|
|
221
|
+
return l;
|
|
222
|
+
}
|
|
223
|
+
function getBitmapShaderLocs(gl, prog) {
|
|
224
|
+
let l = _bitmapLocsByProg.get(prog);
|
|
225
|
+
if (!l) {
|
|
226
|
+
l = {
|
|
227
|
+
ndc: gl.getAttribLocation(prog, "a_ndc"),
|
|
228
|
+
uv: gl.getAttribLocation(prog, "a_uv"),
|
|
229
|
+
texture: gl.getUniformLocation(prog, "u_texture"),
|
|
230
|
+
alphaThreshold: gl.getUniformLocation(prog, "u_alphaThreshold"),
|
|
231
|
+
invertAlpha: gl.getUniformLocation(prog, "u_invertAlpha")
|
|
232
|
+
};
|
|
233
|
+
_bitmapLocsByProg.set(prog, l);
|
|
234
|
+
}
|
|
235
|
+
return l;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Resolves the `cornerRadius` field to `[tl, tr, br, bl]` order matching the
|
|
239
|
+
* `u_radii` vec4 uniform layout.
|
|
240
|
+
* @param r - Raw corner radius value from the clip shape.
|
|
241
|
+
* @returns Tuple `[tl, tr, br, bl]`.
|
|
242
|
+
*/
|
|
243
|
+
function resolveRadii(r) {
|
|
244
|
+
if (!r) return [
|
|
245
|
+
0,
|
|
246
|
+
0,
|
|
247
|
+
0,
|
|
248
|
+
0
|
|
249
|
+
];
|
|
250
|
+
if (typeof r === "number") return [
|
|
251
|
+
r,
|
|
252
|
+
r,
|
|
253
|
+
r,
|
|
254
|
+
r
|
|
255
|
+
];
|
|
256
|
+
return [
|
|
257
|
+
r.tl ?? 0,
|
|
258
|
+
r.tr ?? 0,
|
|
259
|
+
r.br ?? 0,
|
|
260
|
+
r.bl ?? 0
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
/** Returns true when a source/update selects the bitmap mask renderer. */
|
|
264
|
+
function isBitmapStencilClipSource(source) {
|
|
265
|
+
return source.kind === "bitmap";
|
|
266
|
+
}
|
|
267
|
+
function toRoundRectState(source) {
|
|
268
|
+
return {
|
|
269
|
+
kind: "roundRect",
|
|
270
|
+
width: source.width,
|
|
271
|
+
height: source.height,
|
|
272
|
+
offsetX: source.offsetX ?? 0,
|
|
273
|
+
offsetY: source.offsetY ?? 0,
|
|
274
|
+
radii: source.kind === "rect" ? [
|
|
275
|
+
0,
|
|
276
|
+
0,
|
|
277
|
+
0,
|
|
278
|
+
0
|
|
279
|
+
] : resolveRadii(source.cornerRadius)
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function toBitmapState(source) {
|
|
283
|
+
return {
|
|
284
|
+
kind: "bitmap",
|
|
285
|
+
texture: source.texture,
|
|
286
|
+
frame: source.frame,
|
|
287
|
+
width: source.width,
|
|
288
|
+
height: source.height,
|
|
289
|
+
offsetX: source.offsetX ?? 0,
|
|
290
|
+
offsetY: source.offsetY ?? 0,
|
|
291
|
+
alphaThreshold: source.alphaThreshold ?? .5,
|
|
292
|
+
invertAlpha: source.invertAlpha ?? false
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function toMaskState(source) {
|
|
296
|
+
return isBitmapStencilClipSource(source) ? toBitmapState(source) : toRoundRectState(source);
|
|
297
|
+
}
|
|
298
|
+
function mergeMaskState(current, update) {
|
|
299
|
+
if (isBitmapStencilClipSource(update)) {
|
|
300
|
+
if (current.kind !== "bitmap" || update.texture !== void 0) return toBitmapState(update);
|
|
301
|
+
return {
|
|
302
|
+
kind: "bitmap",
|
|
303
|
+
texture: current.texture,
|
|
304
|
+
frame: update.frame !== void 0 ? update.frame : current.frame,
|
|
305
|
+
width: update.width !== void 0 ? update.width : current.width,
|
|
306
|
+
height: update.height !== void 0 ? update.height : current.height,
|
|
307
|
+
offsetX: update.offsetX !== void 0 ? update.offsetX : current.offsetX,
|
|
308
|
+
offsetY: update.offsetY !== void 0 ? update.offsetY : current.offsetY,
|
|
309
|
+
alphaThreshold: update.alphaThreshold !== void 0 ? update.alphaThreshold : current.alphaThreshold,
|
|
310
|
+
invertAlpha: update.invertAlpha !== void 0 ? update.invertAlpha : current.invertAlpha
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
if (current.kind === "bitmap" && update.kind === void 0) {
|
|
314
|
+
const bitmapUpdate = update;
|
|
315
|
+
return {
|
|
316
|
+
kind: "bitmap",
|
|
317
|
+
texture: current.texture,
|
|
318
|
+
frame: bitmapUpdate.frame !== void 0 ? bitmapUpdate.frame : current.frame,
|
|
319
|
+
width: bitmapUpdate.width !== void 0 ? bitmapUpdate.width : current.width,
|
|
320
|
+
height: bitmapUpdate.height !== void 0 ? bitmapUpdate.height : current.height,
|
|
321
|
+
offsetX: bitmapUpdate.offsetX !== void 0 ? bitmapUpdate.offsetX : current.offsetX,
|
|
322
|
+
offsetY: bitmapUpdate.offsetY !== void 0 ? bitmapUpdate.offsetY : current.offsetY,
|
|
323
|
+
alphaThreshold: bitmapUpdate.alphaThreshold !== void 0 ? bitmapUpdate.alphaThreshold : current.alphaThreshold,
|
|
324
|
+
invertAlpha: bitmapUpdate.invertAlpha !== void 0 ? bitmapUpdate.invertAlpha : current.invertAlpha
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
if (current.kind === "bitmap") return toRoundRectState(update);
|
|
328
|
+
const roundUpdate = update;
|
|
329
|
+
return {
|
|
330
|
+
kind: "roundRect",
|
|
331
|
+
width: roundUpdate.width !== void 0 ? roundUpdate.width : current.width,
|
|
332
|
+
height: roundUpdate.height !== void 0 ? roundUpdate.height : current.height,
|
|
333
|
+
offsetX: roundUpdate.offsetX !== void 0 ? roundUpdate.offsetX : current.offsetX,
|
|
334
|
+
offsetY: roundUpdate.offsetY !== void 0 ? roundUpdate.offsetY : current.offsetY,
|
|
335
|
+
radii: roundUpdate.kind === "rect" ? [
|
|
336
|
+
0,
|
|
337
|
+
0,
|
|
338
|
+
0,
|
|
339
|
+
0
|
|
340
|
+
] : "cornerRadius" in roundUpdate ? resolveRadii(roundUpdate.cornerRadius) : current.radii
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
/** Stride in bytes: 4 floats × 4 bytes. */
|
|
344
|
+
var STRIDE = 16;
|
|
345
|
+
/**
|
|
346
|
+
* Transforms the four clip-rect corners through the container's world matrix,
|
|
347
|
+
* uploads them to the vertex buffer, and draws the quad into the stencil.
|
|
348
|
+
*
|
|
349
|
+
* Vertex buffer layout (16 floats / 4 vertices, TRIANGLE_FAN, TL→TR→BR→BL):
|
|
350
|
+
* [ndcX, ndcY, locX, locY] × 4
|
|
351
|
+
* where locX/Y is the position relative to the clip rect's center (for SDF).
|
|
352
|
+
*
|
|
353
|
+
* The three GL states tracked by Phaser's `WebGLGlobalWrapper`
|
|
354
|
+
* (`CURRENT_PROGRAM`, `ARRAY_BUFFER_BINDING`, `VERTEX_ARRAY_BINDING`) are
|
|
355
|
+
* saved and restored so its internal caches stay consistent.
|
|
356
|
+
*
|
|
357
|
+
* @param gl - The (VAO-polyfilled) WebGL context.
|
|
358
|
+
* @param matrix - Container's current world transform matrix.
|
|
359
|
+
* @param offsetX - Top-left X of the clip rect in local space.
|
|
360
|
+
* @param offsetY - Top-left Y of the clip rect in local space.
|
|
361
|
+
* @param w - Clip rect width in local units.
|
|
362
|
+
* @param h - Clip rect height in local units.
|
|
363
|
+
* @param logW - Logical game width (`renderer.width`).
|
|
364
|
+
* @param logH - Logical game height (`renderer.height`).
|
|
365
|
+
* @param radii - Per-corner radii tuple `[tl, tr, br, bl]`.
|
|
366
|
+
* @param vertBuf - Persistent WebGLBuffer (64 bytes, DYNAMIC_DRAW).
|
|
367
|
+
* @param verts - Reusable Float32Array(16) to avoid per-frame allocation.
|
|
368
|
+
*/
|
|
369
|
+
function drawRoundRectMaskShape(gl, matrix, offsetX, offsetY, w, h, logW, logH, radii, vertBuf, verts) {
|
|
370
|
+
const { a, b, c, d, tx, ty } = matrix;
|
|
371
|
+
const hw = w / 2;
|
|
372
|
+
const hh = h / 2;
|
|
373
|
+
const cx = offsetX + hw;
|
|
374
|
+
const cy = offsetY + hh;
|
|
375
|
+
const corners = [
|
|
376
|
+
[cx - hw, cy - hh],
|
|
377
|
+
[cx + hw, cy - hh],
|
|
378
|
+
[cx + hw, cy + hh],
|
|
379
|
+
[cx - hw, cy + hh]
|
|
380
|
+
];
|
|
381
|
+
for (let i = 0; i < 4; i++) {
|
|
382
|
+
const corner = corners[i];
|
|
383
|
+
const lx = corner[0];
|
|
384
|
+
const ly = corner[1];
|
|
385
|
+
const wx = a * lx + c * ly + tx;
|
|
386
|
+
const wy = b * lx + d * ly + ty;
|
|
387
|
+
verts[i * 4 + 0] = wx / logW * 2 - 1;
|
|
388
|
+
verts[i * 4 + 1] = 1 - wy / logH * 2;
|
|
389
|
+
verts[i * 4 + 2] = lx - cx;
|
|
390
|
+
verts[i * 4 + 3] = ly - cy;
|
|
391
|
+
}
|
|
392
|
+
const prevProg = gl.getParameter(gl.CURRENT_PROGRAM);
|
|
393
|
+
const prevBuf = gl.getParameter(gl.ARRAY_BUFFER_BINDING);
|
|
394
|
+
const prevVAO = gl.getParameter(34229);
|
|
395
|
+
gl.bindVertexArray(null);
|
|
396
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
|
|
397
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, verts);
|
|
398
|
+
const prog = getRoundRectProg(gl);
|
|
399
|
+
gl.useProgram(prog);
|
|
400
|
+
const locs = getRoundRectShaderLocs(gl, prog);
|
|
401
|
+
gl.enableVertexAttribArray(locs.ndc);
|
|
402
|
+
gl.vertexAttribPointer(locs.ndc, 2, gl.FLOAT, false, STRIDE, 0);
|
|
403
|
+
gl.enableVertexAttribArray(locs.loc);
|
|
404
|
+
gl.vertexAttribPointer(locs.loc, 2, gl.FLOAT, false, STRIDE, 8);
|
|
405
|
+
gl.uniform2f(locs.halfSize, hw, hh);
|
|
406
|
+
gl.uniform4f(locs.radii, radii[0], radii[1], radii[2], radii[3]);
|
|
407
|
+
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
|
408
|
+
gl.disableVertexAttribArray(locs.ndc);
|
|
409
|
+
gl.disableVertexAttribArray(locs.loc);
|
|
410
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, prevBuf);
|
|
411
|
+
gl.useProgram(prevProg);
|
|
412
|
+
gl.bindVertexArray(prevVAO);
|
|
413
|
+
}
|
|
414
|
+
function isTextureFrame(value) {
|
|
415
|
+
return typeof value === "object" && value !== null && "glTexture" in value && "u0" in value;
|
|
416
|
+
}
|
|
417
|
+
function resolveBitmapFrame(scene, source) {
|
|
418
|
+
const frame = isTextureFrame(source.texture) ? source.texture : typeof source.texture === "string" ? scene.textures.getFrame(source.texture, source.frame) : source.texture.get(source.frame);
|
|
419
|
+
if (!frame?.glTexture?.webGLTexture) return null;
|
|
420
|
+
return {
|
|
421
|
+
webGLTexture: frame.glTexture.webGLTexture,
|
|
422
|
+
width: source.width ?? frame.cutWidth ?? frame.realWidth,
|
|
423
|
+
height: source.height ?? frame.cutHeight ?? frame.realHeight,
|
|
424
|
+
u0: frame.u0,
|
|
425
|
+
v0: frame.v0,
|
|
426
|
+
u1: frame.u1,
|
|
427
|
+
v1: frame.v1
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
function drawBitmapMaskShape(gl, matrix, source, frameInfo, logW, logH, vertBuf, verts) {
|
|
431
|
+
const { a, b, c, d, tx, ty } = matrix;
|
|
432
|
+
const x0 = source.offsetX;
|
|
433
|
+
const y0 = source.offsetY;
|
|
434
|
+
const x1 = x0 + frameInfo.width;
|
|
435
|
+
const y1 = y0 + frameInfo.height;
|
|
436
|
+
const corners = [
|
|
437
|
+
[
|
|
438
|
+
x0,
|
|
439
|
+
y0,
|
|
440
|
+
frameInfo.u0,
|
|
441
|
+
frameInfo.v0
|
|
442
|
+
],
|
|
443
|
+
[
|
|
444
|
+
x1,
|
|
445
|
+
y0,
|
|
446
|
+
frameInfo.u1,
|
|
447
|
+
frameInfo.v0
|
|
448
|
+
],
|
|
449
|
+
[
|
|
450
|
+
x1,
|
|
451
|
+
y1,
|
|
452
|
+
frameInfo.u1,
|
|
453
|
+
frameInfo.v1
|
|
454
|
+
],
|
|
455
|
+
[
|
|
456
|
+
x0,
|
|
457
|
+
y1,
|
|
458
|
+
frameInfo.u0,
|
|
459
|
+
frameInfo.v1
|
|
460
|
+
]
|
|
461
|
+
];
|
|
462
|
+
for (let i = 0; i < 4; i++) {
|
|
463
|
+
const corner = corners[i];
|
|
464
|
+
const lx = corner[0];
|
|
465
|
+
const ly = corner[1];
|
|
466
|
+
const wx = a * lx + c * ly + tx;
|
|
467
|
+
const wy = b * lx + d * ly + ty;
|
|
468
|
+
verts[i * 4 + 0] = wx / logW * 2 - 1;
|
|
469
|
+
verts[i * 4 + 1] = 1 - wy / logH * 2;
|
|
470
|
+
verts[i * 4 + 2] = corner[2];
|
|
471
|
+
verts[i * 4 + 3] = corner[3];
|
|
472
|
+
}
|
|
473
|
+
const prevProg = gl.getParameter(gl.CURRENT_PROGRAM);
|
|
474
|
+
const prevBuf = gl.getParameter(gl.ARRAY_BUFFER_BINDING);
|
|
475
|
+
const prevVAO = gl.getParameter(34229);
|
|
476
|
+
const prevActiveTexture = gl.getParameter(gl.ACTIVE_TEXTURE);
|
|
477
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
478
|
+
const prevTexture = gl.getParameter(gl.TEXTURE_BINDING_2D);
|
|
479
|
+
gl.bindVertexArray(null);
|
|
480
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
|
|
481
|
+
gl.bufferSubData(gl.ARRAY_BUFFER, 0, verts);
|
|
482
|
+
const prog = getBitmapProg(gl);
|
|
483
|
+
gl.useProgram(prog);
|
|
484
|
+
const locs = getBitmapShaderLocs(gl, prog);
|
|
485
|
+
gl.enableVertexAttribArray(locs.ndc);
|
|
486
|
+
gl.vertexAttribPointer(locs.ndc, 2, gl.FLOAT, false, STRIDE, 0);
|
|
487
|
+
gl.enableVertexAttribArray(locs.uv);
|
|
488
|
+
gl.vertexAttribPointer(locs.uv, 2, gl.FLOAT, false, STRIDE, 8);
|
|
489
|
+
gl.bindTexture(gl.TEXTURE_2D, frameInfo.webGLTexture);
|
|
490
|
+
gl.uniform1i(locs.texture, 0);
|
|
491
|
+
gl.uniform1f(locs.alphaThreshold, source.alphaThreshold);
|
|
492
|
+
gl.uniform1f(locs.invertAlpha, source.invertAlpha ? 1 : 0);
|
|
493
|
+
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
|
494
|
+
gl.disableVertexAttribArray(locs.ndc);
|
|
495
|
+
gl.disableVertexAttribArray(locs.uv);
|
|
496
|
+
gl.bindTexture(gl.TEXTURE_2D, prevTexture);
|
|
497
|
+
gl.activeTexture(prevActiveTexture);
|
|
498
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, prevBuf);
|
|
499
|
+
gl.useProgram(prevProg);
|
|
500
|
+
gl.bindVertexArray(prevVAO);
|
|
501
|
+
}
|
|
502
|
+
function drawMaskShape(gl, scene, matrix, source, logW, logH, vertBuf, verts) {
|
|
503
|
+
if (source.kind === "bitmap") {
|
|
504
|
+
const frameInfo = resolveBitmapFrame(scene, source);
|
|
505
|
+
if (!frameInfo) return;
|
|
506
|
+
drawBitmapMaskShape(gl, matrix, source, frameInfo, logW, logH, vertBuf, verts);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
drawRoundRectMaskShape(gl, matrix, source.offsetX, source.offsetY, source.width, source.height, logW, logH, source.radii, vertBuf, verts);
|
|
510
|
+
}
|
|
511
|
+
var STENCIL_HANDLE = Symbol("stencilClipHandle");
|
|
512
|
+
/** Returns the active stencil clip handle attached to a container, if any. */
|
|
513
|
+
function getStencilClipHandle(container) {
|
|
514
|
+
return container[STENCIL_HANDLE];
|
|
515
|
+
}
|
|
516
|
+
/** Removes any active stencil clip from a container. */
|
|
517
|
+
function clearStencilClip(container) {
|
|
518
|
+
getStencilClipHandle(container)?.destroy();
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Applies a WebGL stencil-buffer clip to a Phaser 4 Container.
|
|
522
|
+
*
|
|
523
|
+
* The clip rectangle is expressed in the container's **local coordinate
|
|
524
|
+
* space**. `offsetX`/`offsetY` mark the top-left corner (default 0/0), so
|
|
525
|
+
* a container whose visual area starts at its local origin can be clipped with
|
|
526
|
+
* `applyStencilClip(container, { width, height })`.
|
|
527
|
+
*
|
|
528
|
+
* **Nesting** is handled transparently: each clip level occupies one stencil
|
|
529
|
+
* value (0 = no clip, 1 = depth 0, 2 = depth 1, …). Child clips are always
|
|
530
|
+
* the intersection of their own shape and all ancestor shapes.
|
|
531
|
+
*
|
|
532
|
+
* **Transforms** (translate, scale, rotation) are re-evaluated from
|
|
533
|
+
* `container.getWorldTransformMatrix()` on every frame, so animated or
|
|
534
|
+
* scroll-driven containers clip correctly without any manual update call.
|
|
535
|
+
*
|
|
536
|
+
* If a stencil clip is already attached to the container, calling this
|
|
537
|
+
* function again calls `handle.update(shape)` on the existing handle and
|
|
538
|
+
* returns it.
|
|
539
|
+
*
|
|
540
|
+
* @param container - The container to clip.
|
|
541
|
+
* @param source - Clip source in the container's local coordinate space.
|
|
542
|
+
* @returns A handle to modify dimensions / corner radii or remove the clip.
|
|
543
|
+
*/
|
|
544
|
+
function applyStencilClip(container, source) {
|
|
545
|
+
const obj = container;
|
|
546
|
+
if (obj[STENCIL_HANDLE]) {
|
|
547
|
+
obj[STENCIL_HANDLE].update(source);
|
|
548
|
+
return obj[STENCIL_HANDLE];
|
|
549
|
+
}
|
|
550
|
+
if (container.scene.renderer.type !== phaser.WEBGL) return {
|
|
551
|
+
update() {},
|
|
552
|
+
destroy() {}
|
|
553
|
+
};
|
|
554
|
+
const gl = container.scene.renderer.gl;
|
|
555
|
+
ensurePrerenderReset(gl, container.scene.game);
|
|
556
|
+
ensureFboPatch(gl);
|
|
557
|
+
const vertBuf = gl.createBuffer();
|
|
558
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuf);
|
|
559
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(16), gl.DYNAMIC_DRAW);
|
|
560
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
561
|
+
const verts = new Float32Array(16);
|
|
562
|
+
let maskSource = toMaskState(source);
|
|
563
|
+
let destroyed = false;
|
|
564
|
+
const wrapper = (webglRenderer, go, drawingContext, parentMatrix, renderStep = 0, displayList, displayListIndex) => {
|
|
565
|
+
const renderNext = () => {
|
|
566
|
+
go.renderWebGLStep(webglRenderer, go, drawingContext, parentMatrix, renderStep + 1, displayList, displayListIndex);
|
|
567
|
+
};
|
|
568
|
+
if (destroyed) {
|
|
569
|
+
renderNext();
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const matrix = container.getWorldTransformMatrix();
|
|
573
|
+
const rn = webglRenderer.renderNodes;
|
|
574
|
+
const depth = getDepth(gl);
|
|
575
|
+
const myDepth = depth.value++;
|
|
576
|
+
const logW = webglRenderer.width;
|
|
577
|
+
const logH = webglRenderer.height;
|
|
578
|
+
rn.finishBatch();
|
|
579
|
+
if (myDepth === 0) gl.enable(gl.STENCIL_TEST);
|
|
580
|
+
gl.colorMask(false, false, false, false);
|
|
581
|
+
gl.stencilMask(255);
|
|
582
|
+
gl.stencilFunc(gl.EQUAL, myDepth, 255);
|
|
583
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
|
|
584
|
+
drawMaskShape(gl, container.scene, matrix, maskSource, logW, logH, vertBuf, verts);
|
|
585
|
+
gl.colorMask(true, true, true, true);
|
|
586
|
+
gl.stencilFunc(gl.EQUAL, myDepth + 1, 255);
|
|
587
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
|
588
|
+
gl.stencilMask(0);
|
|
589
|
+
renderNext();
|
|
590
|
+
rn.finishBatch();
|
|
591
|
+
gl.colorMask(false, false, false, false);
|
|
592
|
+
gl.stencilMask(255);
|
|
593
|
+
gl.stencilFunc(gl.EQUAL, myDepth + 1, 255);
|
|
594
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.DECR);
|
|
595
|
+
drawMaskShape(gl, container.scene, matrix, maskSource, logW, logH, vertBuf, verts);
|
|
596
|
+
gl.colorMask(true, true, true, true);
|
|
597
|
+
depth.value--;
|
|
598
|
+
if (myDepth === 0) {
|
|
599
|
+
gl.disable(gl.STENCIL_TEST);
|
|
600
|
+
gl.stencilMask(255);
|
|
601
|
+
} else {
|
|
602
|
+
gl.stencilFunc(gl.EQUAL, myDepth, 255);
|
|
603
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
|
604
|
+
gl.stencilMask(0);
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
const handle = {
|
|
608
|
+
update(s) {
|
|
609
|
+
maskSource = mergeMaskState(maskSource, s);
|
|
610
|
+
},
|
|
611
|
+
destroy() {
|
|
612
|
+
if (destroyed) return;
|
|
613
|
+
destroyed = true;
|
|
614
|
+
gl.deleteBuffer(vertBuf);
|
|
615
|
+
const index = obj._renderSteps.indexOf(wrapper);
|
|
616
|
+
if (index !== -1) obj._renderSteps.splice(index, 1);
|
|
617
|
+
delete obj[STENCIL_HANDLE];
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
obj.addRenderStep(wrapper, 0);
|
|
621
|
+
obj[STENCIL_HANDLE] = handle;
|
|
622
|
+
return handle;
|
|
623
|
+
}
|
|
624
|
+
//#endregion
|
|
625
|
+
//#region src/clip/stencil-clip-extension.ts
|
|
626
|
+
function addMethod(proto, methodName, fn) {
|
|
627
|
+
const hadOwn = Object.prototype.hasOwnProperty.call(proto, methodName);
|
|
628
|
+
const previous = proto[methodName];
|
|
629
|
+
if (!hadOwn) proto[methodName] = fn;
|
|
630
|
+
return { restore: () => {
|
|
631
|
+
if (!hadOwn) delete proto[methodName];
|
|
632
|
+
else proto[methodName] = previous;
|
|
633
|
+
} };
|
|
634
|
+
}
|
|
635
|
+
function wrapMethod(proto, methodName, wrapper) {
|
|
636
|
+
const original = proto[methodName];
|
|
637
|
+
if (typeof original !== "function") return { restore() {} };
|
|
638
|
+
const wrapped = function(...args) {
|
|
639
|
+
return wrapper(original, this, ...args);
|
|
640
|
+
};
|
|
641
|
+
proto[methodName] = wrapped;
|
|
642
|
+
return { restore: () => {
|
|
643
|
+
proto[methodName] = original;
|
|
644
|
+
} };
|
|
645
|
+
}
|
|
646
|
+
var installed = false;
|
|
647
|
+
var restoreHandles = [];
|
|
648
|
+
function canCreateStencilClipSource(source) {
|
|
649
|
+
if (source.kind === "bitmap") return source.texture !== void 0;
|
|
650
|
+
return source.width !== void 0 && source.height !== void 0;
|
|
651
|
+
}
|
|
652
|
+
/** Installs stencil clip helpers on Phaser.GameObjects.Container.prototype. */
|
|
653
|
+
function installStencilClipExtension() {
|
|
654
|
+
if (installed) return;
|
|
655
|
+
installed = true;
|
|
656
|
+
const proto = phaser.GameObjects.Container.prototype;
|
|
657
|
+
restoreHandles.push(addMethod(proto, "setStencilClip", function(source) {
|
|
658
|
+
applyStencilClip(this, source);
|
|
659
|
+
return this;
|
|
660
|
+
}));
|
|
661
|
+
restoreHandles.push(addMethod(proto, "updateStencilClip", function(source) {
|
|
662
|
+
const handle = getStencilClipHandle(this);
|
|
663
|
+
if (handle) handle.update(source);
|
|
664
|
+
else if (canCreateStencilClipSource(source)) applyStencilClip(this, source);
|
|
665
|
+
return this;
|
|
666
|
+
}));
|
|
667
|
+
restoreHandles.push(addMethod(proto, "clearStencilClip", function() {
|
|
668
|
+
clearStencilClip(this);
|
|
669
|
+
return this;
|
|
670
|
+
}));
|
|
671
|
+
restoreHandles.push(addMethod(proto, "getStencilClipHandle", function() {
|
|
672
|
+
return getStencilClipHandle(this);
|
|
673
|
+
}));
|
|
674
|
+
restoreHandles.push(wrapMethod(proto, "destroy", (original, self, ...args) => {
|
|
675
|
+
clearStencilClip(self);
|
|
676
|
+
return original.apply(self, args);
|
|
677
|
+
}));
|
|
678
|
+
}
|
|
679
|
+
/** Restores Phaser prototypes to their previous state. Intended for tests/HMR. */
|
|
680
|
+
function uninstallStencilClipExtension() {
|
|
681
|
+
for (const handle of [...restoreHandles].reverse()) handle.restore();
|
|
682
|
+
restoreHandles = [];
|
|
683
|
+
installed = false;
|
|
684
|
+
}
|
|
685
|
+
//#endregion
|
|
686
|
+
//#region src/clip/index.ts
|
|
687
|
+
installStencilClipExtension();
|
|
688
|
+
//#endregion
|
|
689
|
+
exports.applyStencilClip = applyStencilClip;
|
|
690
|
+
exports.clearStencilClip = clearStencilClip;
|
|
691
|
+
exports.getStencilClipHandle = getStencilClipHandle;
|
|
692
|
+
exports.installStencilClipExtension = installStencilClipExtension;
|
|
693
|
+
exports.isBitmapStencilClipSource = isBitmapStencilClipSource;
|
|
694
|
+
exports.uninstallStencilClipExtension = uninstallStencilClipExtension;
|
|
695
|
+
|
|
696
|
+
//# sourceMappingURL=index.cjs.map
|