@vunk/plus 1.11.0 → 1.11.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.
- package/components/bubble-list/index.mjs +1 -1
- package/components/drawer/index.mjs +1 -1
- package/components/index2.mjs +64 -26423
- package/components/pixi-frame/index.mjs +1988 -7
- package/package.json +1 -1
- package/components/SharedSystems.mjs +0 -3413
- package/components/WebGLRenderer.mjs +0 -3631
- package/components/WebGPURenderer.mjs +0 -2143
- package/components/browserAll.mjs +0 -2960
- package/components/colorToUniform.mjs +0 -268
- package/components/index3.mjs +0 -78
- package/components/init.mjs +0 -4505
- package/components/webworkerAll.mjs +0 -10
package/components/init.mjs
DELETED
|
@@ -1,4505 +0,0 @@
|
|
|
1
|
-
import { E as ExtensionType, U as UPDATE_PRIORITY, T as Ticker, a2 as Geometry, k as UniformGroup, c as BindGroup, z as TexturePool, s as Texture, M as Matrix, R as RendererType, w as warn, F as Bounds, a9 as ViewContainer, aa as GraphicsContext, $ as deprecation, a0 as v8_0_0, b as Buffer, B as BufferUsage, D as DOMAdapter, ab as nextPow2, N as Rectangle, ac as CanvasTextMetrics, l as CanvasPool, ad as fontStringFromTextStyle, Y as Color, ae as getCanvasFillStyle, af as TextStyle, x as BigPool, ag as BatchableGraphics, ah as getAdjustedBlendModeBlend, J as getAttributeInfoFromFormat, ai as ViewableBuffer, q as Shader, t as GlProgram, G as GpuProgram, aj as TextureStyle, m as compileHighShaderGpuProgram, a4 as compileHighShaderGlProgram, p as roundPixelsBit, a7 as roundPixelsBitGl, n as colorBit, o as generateTextureBatchBit, a5 as colorBitGl, a6 as generateTextureBatchBitGl, a8 as getBatchSamplersUniformGroup, ak as BitmapFontManager, al as getBitmapTextLayout, am as Cache, an as updateQuadBounds, e as extensions, ao as GraphicsContextSystem } from './index2.mjs';
|
|
2
|
-
import { S as State, c as color32BitToUniform, a as localUniformBit, b as localUniformBitGl, B as BatchableSprite } from './colorToUniform.mjs';
|
|
3
|
-
|
|
4
|
-
class ResizePlugin {
|
|
5
|
-
/**
|
|
6
|
-
* Initialize the plugin with scope of application instance
|
|
7
|
-
* @private
|
|
8
|
-
* @param {object} [options] - See application options
|
|
9
|
-
*/
|
|
10
|
-
static init(options) {
|
|
11
|
-
Object.defineProperty(
|
|
12
|
-
this,
|
|
13
|
-
"resizeTo",
|
|
14
|
-
{
|
|
15
|
-
set(dom) {
|
|
16
|
-
globalThis.removeEventListener("resize", this.queueResize);
|
|
17
|
-
this._resizeTo = dom;
|
|
18
|
-
if (dom) {
|
|
19
|
-
globalThis.addEventListener("resize", this.queueResize);
|
|
20
|
-
this.resize();
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
get() {
|
|
24
|
-
return this._resizeTo;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
);
|
|
28
|
-
this.queueResize = () => {
|
|
29
|
-
if (!this._resizeTo) {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
this._cancelResize();
|
|
33
|
-
this._resizeId = requestAnimationFrame(() => this.resize());
|
|
34
|
-
};
|
|
35
|
-
this._cancelResize = () => {
|
|
36
|
-
if (this._resizeId) {
|
|
37
|
-
cancelAnimationFrame(this._resizeId);
|
|
38
|
-
this._resizeId = null;
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
this.resize = () => {
|
|
42
|
-
if (!this._resizeTo) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
this._cancelResize();
|
|
46
|
-
let width;
|
|
47
|
-
let height;
|
|
48
|
-
if (this._resizeTo === globalThis.window) {
|
|
49
|
-
width = globalThis.innerWidth;
|
|
50
|
-
height = globalThis.innerHeight;
|
|
51
|
-
} else {
|
|
52
|
-
const { clientWidth, clientHeight } = this._resizeTo;
|
|
53
|
-
width = clientWidth;
|
|
54
|
-
height = clientHeight;
|
|
55
|
-
}
|
|
56
|
-
this.renderer.resize(width, height);
|
|
57
|
-
this.render();
|
|
58
|
-
};
|
|
59
|
-
this._resizeId = null;
|
|
60
|
-
this._resizeTo = null;
|
|
61
|
-
this.resizeTo = options.resizeTo || null;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Clean up the ticker, scoped to application
|
|
65
|
-
* @private
|
|
66
|
-
*/
|
|
67
|
-
static destroy() {
|
|
68
|
-
globalThis.removeEventListener("resize", this.queueResize);
|
|
69
|
-
this._cancelResize();
|
|
70
|
-
this._cancelResize = null;
|
|
71
|
-
this.queueResize = null;
|
|
72
|
-
this.resizeTo = null;
|
|
73
|
-
this.resize = null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
/** @ignore */
|
|
77
|
-
ResizePlugin.extension = ExtensionType.Application;
|
|
78
|
-
|
|
79
|
-
class TickerPlugin {
|
|
80
|
-
/**
|
|
81
|
-
* Initialize the plugin with scope of application instance
|
|
82
|
-
* @private
|
|
83
|
-
* @param {object} [options] - See application options
|
|
84
|
-
*/
|
|
85
|
-
static init(options) {
|
|
86
|
-
options = Object.assign({
|
|
87
|
-
autoStart: true,
|
|
88
|
-
sharedTicker: false
|
|
89
|
-
}, options);
|
|
90
|
-
Object.defineProperty(
|
|
91
|
-
this,
|
|
92
|
-
"ticker",
|
|
93
|
-
{
|
|
94
|
-
set(ticker) {
|
|
95
|
-
if (this._ticker) {
|
|
96
|
-
this._ticker.remove(this.render, this);
|
|
97
|
-
}
|
|
98
|
-
this._ticker = ticker;
|
|
99
|
-
if (ticker) {
|
|
100
|
-
ticker.add(this.render, this, UPDATE_PRIORITY.LOW);
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
get() {
|
|
104
|
-
return this._ticker;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
this.stop = () => {
|
|
109
|
-
this._ticker.stop();
|
|
110
|
-
};
|
|
111
|
-
this.start = () => {
|
|
112
|
-
this._ticker.start();
|
|
113
|
-
};
|
|
114
|
-
this._ticker = null;
|
|
115
|
-
this.ticker = options.sharedTicker ? Ticker.shared : new Ticker();
|
|
116
|
-
if (options.autoStart) {
|
|
117
|
-
this.start();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Clean up the ticker, scoped to application.
|
|
122
|
-
* @private
|
|
123
|
-
*/
|
|
124
|
-
static destroy() {
|
|
125
|
-
if (this._ticker) {
|
|
126
|
-
const oldTicker = this._ticker;
|
|
127
|
-
this.ticker = null;
|
|
128
|
-
oldTicker.destroy();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
/** @ignore */
|
|
133
|
-
TickerPlugin.extension = ExtensionType.Application;
|
|
134
|
-
|
|
135
|
-
class FilterPipe {
|
|
136
|
-
constructor(renderer) {
|
|
137
|
-
this._renderer = renderer;
|
|
138
|
-
}
|
|
139
|
-
push(filterEffect, container, instructionSet) {
|
|
140
|
-
const renderPipes = this._renderer.renderPipes;
|
|
141
|
-
renderPipes.batch.break(instructionSet);
|
|
142
|
-
instructionSet.add({
|
|
143
|
-
renderPipeId: "filter",
|
|
144
|
-
canBundle: false,
|
|
145
|
-
action: "pushFilter",
|
|
146
|
-
container,
|
|
147
|
-
filterEffect
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
pop(_filterEffect, _container, instructionSet) {
|
|
151
|
-
this._renderer.renderPipes.batch.break(instructionSet);
|
|
152
|
-
instructionSet.add({
|
|
153
|
-
renderPipeId: "filter",
|
|
154
|
-
action: "popFilter",
|
|
155
|
-
canBundle: false
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
execute(instruction) {
|
|
159
|
-
if (instruction.action === "pushFilter") {
|
|
160
|
-
this._renderer.filter.push(instruction);
|
|
161
|
-
} else if (instruction.action === "popFilter") {
|
|
162
|
-
this._renderer.filter.pop();
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
destroy() {
|
|
166
|
-
this._renderer = null;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
FilterPipe.extension = {
|
|
170
|
-
type: [
|
|
171
|
-
ExtensionType.WebGLPipes,
|
|
172
|
-
ExtensionType.WebGPUPipes,
|
|
173
|
-
ExtensionType.CanvasPipes
|
|
174
|
-
],
|
|
175
|
-
name: "filter"
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
function getGlobalRenderableBounds(renderables, bounds) {
|
|
179
|
-
bounds.clear();
|
|
180
|
-
const tempMatrix = bounds.matrix;
|
|
181
|
-
for (let i = 0; i < renderables.length; i++) {
|
|
182
|
-
const renderable = renderables[i];
|
|
183
|
-
if (renderable.globalDisplayStatus < 7) {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
bounds.matrix = renderable.worldTransform;
|
|
187
|
-
bounds.addBounds(renderable.bounds);
|
|
188
|
-
}
|
|
189
|
-
bounds.matrix = tempMatrix;
|
|
190
|
-
return bounds;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const quadGeometry = new Geometry({
|
|
194
|
-
attributes: {
|
|
195
|
-
aPosition: {
|
|
196
|
-
buffer: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
|
|
197
|
-
format: "float32x2",
|
|
198
|
-
stride: 2 * 4,
|
|
199
|
-
offset: 0
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
indexBuffer: new Uint32Array([0, 1, 2, 0, 2, 3])
|
|
203
|
-
});
|
|
204
|
-
class FilterData {
|
|
205
|
-
constructor() {
|
|
206
|
-
/**
|
|
207
|
-
* Indicates whether the filter should be skipped.
|
|
208
|
-
* @type {boolean}
|
|
209
|
-
*/
|
|
210
|
-
this.skip = false;
|
|
211
|
-
/**
|
|
212
|
-
* The texture to which the filter is applied.
|
|
213
|
-
* @type {Texture}
|
|
214
|
-
*/
|
|
215
|
-
this.inputTexture = null;
|
|
216
|
-
/**
|
|
217
|
-
* The back texture used for blending, if required.
|
|
218
|
-
* @type {Texture | null}
|
|
219
|
-
*/
|
|
220
|
-
this.backTexture = null;
|
|
221
|
-
/**
|
|
222
|
-
* The list of filters to be applied.
|
|
223
|
-
* @type {Filter[]}
|
|
224
|
-
*/
|
|
225
|
-
this.filters = null;
|
|
226
|
-
/**
|
|
227
|
-
* The bounds of the filter area.
|
|
228
|
-
* @type {Bounds}
|
|
229
|
-
*/
|
|
230
|
-
this.bounds = new Bounds();
|
|
231
|
-
/**
|
|
232
|
-
* The container to which the filter is applied.
|
|
233
|
-
* @type {Container}
|
|
234
|
-
*/
|
|
235
|
-
this.container = null;
|
|
236
|
-
/**
|
|
237
|
-
* Indicates whether blending is required for the filter.
|
|
238
|
-
* @type {boolean}
|
|
239
|
-
*/
|
|
240
|
-
this.blendRequired = false;
|
|
241
|
-
/**
|
|
242
|
-
* The render surface where the output of the filter is rendered.
|
|
243
|
-
* @type {RenderSurface}
|
|
244
|
-
*/
|
|
245
|
-
this.outputRenderSurface = null;
|
|
246
|
-
/**
|
|
247
|
-
* The global frame of the filter area.
|
|
248
|
-
* @type {{ x: number, y: number, width: number, height: number }}
|
|
249
|
-
*/
|
|
250
|
-
this.globalFrame = { x: 0, y: 0, width: 0, height: 0 };
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
class FilterSystem {
|
|
254
|
-
constructor(renderer) {
|
|
255
|
-
this._filterStackIndex = 0;
|
|
256
|
-
this._filterStack = [];
|
|
257
|
-
this._filterGlobalUniforms = new UniformGroup({
|
|
258
|
-
uInputSize: { value: new Float32Array(4), type: "vec4<f32>" },
|
|
259
|
-
uInputPixel: { value: new Float32Array(4), type: "vec4<f32>" },
|
|
260
|
-
uInputClamp: { value: new Float32Array(4), type: "vec4<f32>" },
|
|
261
|
-
uOutputFrame: { value: new Float32Array(4), type: "vec4<f32>" },
|
|
262
|
-
uGlobalFrame: { value: new Float32Array(4), type: "vec4<f32>" },
|
|
263
|
-
uOutputTexture: { value: new Float32Array(4), type: "vec4<f32>" }
|
|
264
|
-
});
|
|
265
|
-
this._globalFilterBindGroup = new BindGroup({});
|
|
266
|
-
this.renderer = renderer;
|
|
267
|
-
}
|
|
268
|
-
/**
|
|
269
|
-
* The back texture of the currently active filter. Requires the filter to have `blendRequired` set to true.
|
|
270
|
-
* @readonly
|
|
271
|
-
*/
|
|
272
|
-
get activeBackTexture() {
|
|
273
|
-
return this._activeFilterData?.backTexture;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* Pushes a filter instruction onto the filter stack.
|
|
277
|
-
* @param instruction - The instruction containing the filter effect and container.
|
|
278
|
-
* @internal
|
|
279
|
-
*/
|
|
280
|
-
push(instruction) {
|
|
281
|
-
const renderer = this.renderer;
|
|
282
|
-
const filters = instruction.filterEffect.filters;
|
|
283
|
-
const filterData = this._pushFilterData();
|
|
284
|
-
filterData.skip = false;
|
|
285
|
-
filterData.filters = filters;
|
|
286
|
-
filterData.container = instruction.container;
|
|
287
|
-
filterData.outputRenderSurface = renderer.renderTarget.renderSurface;
|
|
288
|
-
const colorTextureSource = renderer.renderTarget.renderTarget.colorTexture.source;
|
|
289
|
-
const rootResolution = colorTextureSource.resolution;
|
|
290
|
-
const rootAntialias = colorTextureSource.antialias;
|
|
291
|
-
if (filters.length === 0) {
|
|
292
|
-
filterData.skip = true;
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
const bounds = filterData.bounds;
|
|
296
|
-
this._calculateFilterArea(instruction, bounds);
|
|
297
|
-
this._calculateFilterBounds(filterData, renderer.renderTarget.rootViewPort, rootAntialias, rootResolution, 1);
|
|
298
|
-
if (filterData.skip) {
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const previousFilterData = this._getPreviousFilterData();
|
|
302
|
-
const globalResolution = this._findFilterResolution(rootResolution);
|
|
303
|
-
let offsetX = 0;
|
|
304
|
-
let offsetY = 0;
|
|
305
|
-
if (previousFilterData) {
|
|
306
|
-
offsetX = previousFilterData.bounds.minX;
|
|
307
|
-
offsetY = previousFilterData.bounds.minY;
|
|
308
|
-
}
|
|
309
|
-
this._calculateGlobalFrame(
|
|
310
|
-
filterData,
|
|
311
|
-
offsetX,
|
|
312
|
-
offsetY,
|
|
313
|
-
globalResolution,
|
|
314
|
-
colorTextureSource.width,
|
|
315
|
-
colorTextureSource.height
|
|
316
|
-
);
|
|
317
|
-
this._setupFilterTextures(filterData, bounds, renderer, previousFilterData);
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Applies filters to a texture.
|
|
321
|
-
*
|
|
322
|
-
* This method takes a texture and a list of filters, applies the filters to the texture,
|
|
323
|
-
* and returns the resulting texture.
|
|
324
|
-
* @param {object} params - The parameters for applying filters.
|
|
325
|
-
* @param {Texture} params.texture - The texture to apply filters to.
|
|
326
|
-
* @param {Filter[]} params.filters - The filters to apply.
|
|
327
|
-
* @returns {Texture} The resulting texture after all filters have been applied.
|
|
328
|
-
* @example
|
|
329
|
-
*
|
|
330
|
-
* ```ts
|
|
331
|
-
* // Create a texture and a list of filters
|
|
332
|
-
* const texture = new Texture(...);
|
|
333
|
-
* const filters = [new BlurFilter(), new ColorMatrixFilter()];
|
|
334
|
-
*
|
|
335
|
-
* // Apply the filters to the texture
|
|
336
|
-
* const resultTexture = filterSystem.applyToTexture({ texture, filters });
|
|
337
|
-
*
|
|
338
|
-
* // Use the resulting texture
|
|
339
|
-
* sprite.texture = resultTexture;
|
|
340
|
-
* ```
|
|
341
|
-
*
|
|
342
|
-
* Key Points:
|
|
343
|
-
* 1. padding is not currently supported here - so clipping may occur with filters that use padding.
|
|
344
|
-
* 2. If all filters are disabled or skipped, the original texture is returned.
|
|
345
|
-
*/
|
|
346
|
-
generateFilteredTexture({ texture, filters }) {
|
|
347
|
-
const filterData = this._pushFilterData();
|
|
348
|
-
this._activeFilterData = filterData;
|
|
349
|
-
filterData.skip = false;
|
|
350
|
-
filterData.filters = filters;
|
|
351
|
-
const colorTextureSource = texture.source;
|
|
352
|
-
const rootResolution = colorTextureSource.resolution;
|
|
353
|
-
const rootAntialias = colorTextureSource.antialias;
|
|
354
|
-
if (filters.length === 0) {
|
|
355
|
-
filterData.skip = true;
|
|
356
|
-
return texture;
|
|
357
|
-
}
|
|
358
|
-
const bounds = filterData.bounds;
|
|
359
|
-
bounds.addRect(texture.frame);
|
|
360
|
-
this._calculateFilterBounds(filterData, bounds.rectangle, rootAntialias, rootResolution, 0);
|
|
361
|
-
if (filterData.skip) {
|
|
362
|
-
return texture;
|
|
363
|
-
}
|
|
364
|
-
const globalResolution = rootResolution;
|
|
365
|
-
const offsetX = 0;
|
|
366
|
-
const offsetY = 0;
|
|
367
|
-
this._calculateGlobalFrame(
|
|
368
|
-
filterData,
|
|
369
|
-
offsetX,
|
|
370
|
-
offsetY,
|
|
371
|
-
globalResolution,
|
|
372
|
-
colorTextureSource.width,
|
|
373
|
-
colorTextureSource.height
|
|
374
|
-
);
|
|
375
|
-
filterData.outputRenderSurface = TexturePool.getOptimalTexture(
|
|
376
|
-
bounds.width,
|
|
377
|
-
bounds.height,
|
|
378
|
-
filterData.resolution,
|
|
379
|
-
filterData.antialias
|
|
380
|
-
);
|
|
381
|
-
filterData.backTexture = Texture.EMPTY;
|
|
382
|
-
filterData.inputTexture = texture;
|
|
383
|
-
const renderer = this.renderer;
|
|
384
|
-
renderer.renderTarget.finishRenderPass();
|
|
385
|
-
this._applyFiltersToTexture(filterData, true);
|
|
386
|
-
const outputTexture = filterData.outputRenderSurface;
|
|
387
|
-
outputTexture.source.alphaMode = "premultiplied-alpha";
|
|
388
|
-
return outputTexture;
|
|
389
|
-
}
|
|
390
|
-
/** @internal */
|
|
391
|
-
pop() {
|
|
392
|
-
const renderer = this.renderer;
|
|
393
|
-
const filterData = this._popFilterData();
|
|
394
|
-
if (filterData.skip) {
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
renderer.globalUniforms.pop();
|
|
398
|
-
renderer.renderTarget.finishRenderPass();
|
|
399
|
-
this._activeFilterData = filterData;
|
|
400
|
-
this._applyFiltersToTexture(filterData, false);
|
|
401
|
-
if (filterData.blendRequired) {
|
|
402
|
-
TexturePool.returnTexture(filterData.backTexture);
|
|
403
|
-
}
|
|
404
|
-
TexturePool.returnTexture(filterData.inputTexture);
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Copies the last render surface to a texture.
|
|
408
|
-
* @param lastRenderSurface - The last render surface to copy from.
|
|
409
|
-
* @param bounds - The bounds of the area to copy.
|
|
410
|
-
* @param previousBounds - The previous bounds to use for offsetting the copy.
|
|
411
|
-
*/
|
|
412
|
-
getBackTexture(lastRenderSurface, bounds, previousBounds) {
|
|
413
|
-
const backgroundResolution = lastRenderSurface.colorTexture.source._resolution;
|
|
414
|
-
const backTexture = TexturePool.getOptimalTexture(
|
|
415
|
-
bounds.width,
|
|
416
|
-
bounds.height,
|
|
417
|
-
backgroundResolution,
|
|
418
|
-
false
|
|
419
|
-
);
|
|
420
|
-
let x = bounds.minX;
|
|
421
|
-
let y = bounds.minY;
|
|
422
|
-
if (previousBounds) {
|
|
423
|
-
x -= previousBounds.minX;
|
|
424
|
-
y -= previousBounds.minY;
|
|
425
|
-
}
|
|
426
|
-
x = Math.floor(x * backgroundResolution);
|
|
427
|
-
y = Math.floor(y * backgroundResolution);
|
|
428
|
-
const width = Math.ceil(bounds.width * backgroundResolution);
|
|
429
|
-
const height = Math.ceil(bounds.height * backgroundResolution);
|
|
430
|
-
this.renderer.renderTarget.copyToTexture(
|
|
431
|
-
lastRenderSurface,
|
|
432
|
-
backTexture,
|
|
433
|
-
{ x, y },
|
|
434
|
-
{ width, height },
|
|
435
|
-
{ x: 0, y: 0 }
|
|
436
|
-
);
|
|
437
|
-
return backTexture;
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Applies a filter to a texture.
|
|
441
|
-
* @param filter - The filter to apply.
|
|
442
|
-
* @param input - The input texture.
|
|
443
|
-
* @param output - The output render surface.
|
|
444
|
-
* @param clear - Whether to clear the output surface before applying the filter.
|
|
445
|
-
*/
|
|
446
|
-
applyFilter(filter, input, output, clear) {
|
|
447
|
-
const renderer = this.renderer;
|
|
448
|
-
const filterData = this._activeFilterData;
|
|
449
|
-
const outputRenderSurface = filterData.outputRenderSurface;
|
|
450
|
-
const isFinalTarget = outputRenderSurface === output;
|
|
451
|
-
const rootResolution = renderer.renderTarget.rootRenderTarget.colorTexture.source._resolution;
|
|
452
|
-
const resolution = this._findFilterResolution(rootResolution);
|
|
453
|
-
let offsetX = 0;
|
|
454
|
-
let offsetY = 0;
|
|
455
|
-
if (isFinalTarget) {
|
|
456
|
-
const offset = this._findPreviousFilterOffset();
|
|
457
|
-
offsetX = offset.x;
|
|
458
|
-
offsetY = offset.y;
|
|
459
|
-
}
|
|
460
|
-
this._updateFilterUniforms(input, output, filterData, offsetX, offsetY, resolution, isFinalTarget, clear);
|
|
461
|
-
this._setupBindGroupsAndRender(filter, input, renderer);
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Multiply _input normalized coordinates_ to this matrix to get _sprite texture normalized coordinates_.
|
|
465
|
-
*
|
|
466
|
-
* Use `outputMatrix * vTextureCoord` in the shader.
|
|
467
|
-
* @param outputMatrix - The matrix to output to.
|
|
468
|
-
* @param {Sprite} sprite - The sprite to map to.
|
|
469
|
-
* @returns The mapped matrix.
|
|
470
|
-
*/
|
|
471
|
-
calculateSpriteMatrix(outputMatrix, sprite) {
|
|
472
|
-
const data = this._activeFilterData;
|
|
473
|
-
const mappedMatrix = outputMatrix.set(
|
|
474
|
-
data.inputTexture._source.width,
|
|
475
|
-
0,
|
|
476
|
-
0,
|
|
477
|
-
data.inputTexture._source.height,
|
|
478
|
-
data.bounds.minX,
|
|
479
|
-
data.bounds.minY
|
|
480
|
-
);
|
|
481
|
-
const worldTransform = sprite.worldTransform.copyTo(Matrix.shared);
|
|
482
|
-
const renderGroup = sprite.renderGroup || sprite.parentRenderGroup;
|
|
483
|
-
if (renderGroup && renderGroup.cacheToLocalTransform) {
|
|
484
|
-
worldTransform.prepend(renderGroup.cacheToLocalTransform);
|
|
485
|
-
}
|
|
486
|
-
worldTransform.invert();
|
|
487
|
-
mappedMatrix.prepend(worldTransform);
|
|
488
|
-
mappedMatrix.scale(
|
|
489
|
-
1 / sprite.texture.frame.width,
|
|
490
|
-
1 / sprite.texture.frame.height
|
|
491
|
-
);
|
|
492
|
-
mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y);
|
|
493
|
-
return mappedMatrix;
|
|
494
|
-
}
|
|
495
|
-
destroy() {
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* Sets up the bind groups and renders the filter.
|
|
499
|
-
* @param filter - The filter to apply
|
|
500
|
-
* @param input - The input texture
|
|
501
|
-
* @param renderer - The renderer instance
|
|
502
|
-
*/
|
|
503
|
-
_setupBindGroupsAndRender(filter, input, renderer) {
|
|
504
|
-
if (renderer.renderPipes.uniformBatch) {
|
|
505
|
-
const batchUniforms = renderer.renderPipes.uniformBatch.getUboResource(this._filterGlobalUniforms);
|
|
506
|
-
this._globalFilterBindGroup.setResource(batchUniforms, 0);
|
|
507
|
-
} else {
|
|
508
|
-
this._globalFilterBindGroup.setResource(this._filterGlobalUniforms, 0);
|
|
509
|
-
}
|
|
510
|
-
this._globalFilterBindGroup.setResource(input.source, 1);
|
|
511
|
-
this._globalFilterBindGroup.setResource(input.source.style, 2);
|
|
512
|
-
filter.groups[0] = this._globalFilterBindGroup;
|
|
513
|
-
renderer.encoder.draw({
|
|
514
|
-
geometry: quadGeometry,
|
|
515
|
-
shader: filter,
|
|
516
|
-
state: filter._state,
|
|
517
|
-
topology: "triangle-list"
|
|
518
|
-
});
|
|
519
|
-
if (renderer.type === RendererType.WEBGL) {
|
|
520
|
-
renderer.renderTarget.finishRenderPass();
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
/**
|
|
524
|
-
* Sets up the filter textures including input texture and back texture if needed.
|
|
525
|
-
* @param filterData - The filter data to update
|
|
526
|
-
* @param bounds - The bounds for the texture
|
|
527
|
-
* @param renderer - The renderer instance
|
|
528
|
-
* @param previousFilterData - The previous filter data for back texture calculation
|
|
529
|
-
*/
|
|
530
|
-
_setupFilterTextures(filterData, bounds, renderer, previousFilterData) {
|
|
531
|
-
filterData.backTexture = Texture.EMPTY;
|
|
532
|
-
if (filterData.blendRequired) {
|
|
533
|
-
renderer.renderTarget.finishRenderPass();
|
|
534
|
-
const renderTarget = renderer.renderTarget.getRenderTarget(filterData.outputRenderSurface);
|
|
535
|
-
filterData.backTexture = this.getBackTexture(renderTarget, bounds, previousFilterData?.bounds);
|
|
536
|
-
}
|
|
537
|
-
filterData.inputTexture = TexturePool.getOptimalTexture(
|
|
538
|
-
bounds.width,
|
|
539
|
-
bounds.height,
|
|
540
|
-
filterData.resolution,
|
|
541
|
-
filterData.antialias
|
|
542
|
-
);
|
|
543
|
-
renderer.renderTarget.bind(filterData.inputTexture, true);
|
|
544
|
-
renderer.globalUniforms.push({
|
|
545
|
-
offset: bounds
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
/**
|
|
549
|
-
* Calculates and sets the global frame for the filter.
|
|
550
|
-
* @param filterData - The filter data to update
|
|
551
|
-
* @param offsetX - The X offset
|
|
552
|
-
* @param offsetY - The Y offset
|
|
553
|
-
* @param globalResolution - The global resolution
|
|
554
|
-
* @param sourceWidth - The source texture width
|
|
555
|
-
* @param sourceHeight - The source texture height
|
|
556
|
-
*/
|
|
557
|
-
_calculateGlobalFrame(filterData, offsetX, offsetY, globalResolution, sourceWidth, sourceHeight) {
|
|
558
|
-
const globalFrame = filterData.globalFrame;
|
|
559
|
-
globalFrame.x = offsetX * globalResolution;
|
|
560
|
-
globalFrame.y = offsetY * globalResolution;
|
|
561
|
-
globalFrame.width = sourceWidth * globalResolution;
|
|
562
|
-
globalFrame.height = sourceHeight * globalResolution;
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* Updates the filter uniforms with the current filter state.
|
|
566
|
-
* @param input - The input texture
|
|
567
|
-
* @param output - The output render surface
|
|
568
|
-
* @param filterData - The current filter data
|
|
569
|
-
* @param offsetX - The X offset for positioning
|
|
570
|
-
* @param offsetY - The Y offset for positioning
|
|
571
|
-
* @param resolution - The current resolution
|
|
572
|
-
* @param isFinalTarget - Whether this is the final render target
|
|
573
|
-
* @param clear - Whether to clear the output surface
|
|
574
|
-
*/
|
|
575
|
-
_updateFilterUniforms(input, output, filterData, offsetX, offsetY, resolution, isFinalTarget, clear) {
|
|
576
|
-
const uniforms = this._filterGlobalUniforms.uniforms;
|
|
577
|
-
const outputFrame = uniforms.uOutputFrame;
|
|
578
|
-
const inputSize = uniforms.uInputSize;
|
|
579
|
-
const inputPixel = uniforms.uInputPixel;
|
|
580
|
-
const inputClamp = uniforms.uInputClamp;
|
|
581
|
-
const globalFrame = uniforms.uGlobalFrame;
|
|
582
|
-
const outputTexture = uniforms.uOutputTexture;
|
|
583
|
-
if (isFinalTarget) {
|
|
584
|
-
outputFrame[0] = filterData.bounds.minX - offsetX;
|
|
585
|
-
outputFrame[1] = filterData.bounds.minY - offsetY;
|
|
586
|
-
} else {
|
|
587
|
-
outputFrame[0] = 0;
|
|
588
|
-
outputFrame[1] = 0;
|
|
589
|
-
}
|
|
590
|
-
outputFrame[2] = input.frame.width;
|
|
591
|
-
outputFrame[3] = input.frame.height;
|
|
592
|
-
inputSize[0] = input.source.width;
|
|
593
|
-
inputSize[1] = input.source.height;
|
|
594
|
-
inputSize[2] = 1 / inputSize[0];
|
|
595
|
-
inputSize[3] = 1 / inputSize[1];
|
|
596
|
-
inputPixel[0] = input.source.pixelWidth;
|
|
597
|
-
inputPixel[1] = input.source.pixelHeight;
|
|
598
|
-
inputPixel[2] = 1 / inputPixel[0];
|
|
599
|
-
inputPixel[3] = 1 / inputPixel[1];
|
|
600
|
-
inputClamp[0] = 0.5 * inputPixel[2];
|
|
601
|
-
inputClamp[1] = 0.5 * inputPixel[3];
|
|
602
|
-
inputClamp[2] = input.frame.width * inputSize[2] - 0.5 * inputPixel[2];
|
|
603
|
-
inputClamp[3] = input.frame.height * inputSize[3] - 0.5 * inputPixel[3];
|
|
604
|
-
const rootTexture = this.renderer.renderTarget.rootRenderTarget.colorTexture;
|
|
605
|
-
globalFrame[0] = offsetX * resolution;
|
|
606
|
-
globalFrame[1] = offsetY * resolution;
|
|
607
|
-
globalFrame[2] = rootTexture.source.width * resolution;
|
|
608
|
-
globalFrame[3] = rootTexture.source.height * resolution;
|
|
609
|
-
if (output instanceof Texture)
|
|
610
|
-
output.source.resource = null;
|
|
611
|
-
const renderTarget = this.renderer.renderTarget.getRenderTarget(output);
|
|
612
|
-
this.renderer.renderTarget.bind(output, !!clear);
|
|
613
|
-
if (output instanceof Texture) {
|
|
614
|
-
outputTexture[0] = output.frame.width;
|
|
615
|
-
outputTexture[1] = output.frame.height;
|
|
616
|
-
} else {
|
|
617
|
-
outputTexture[0] = renderTarget.width;
|
|
618
|
-
outputTexture[1] = renderTarget.height;
|
|
619
|
-
}
|
|
620
|
-
outputTexture[2] = renderTarget.isRoot ? -1 : 1;
|
|
621
|
-
this._filterGlobalUniforms.update();
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Finds the correct resolution by looking back through the filter stack.
|
|
625
|
-
* @param rootResolution - The fallback root resolution to use
|
|
626
|
-
* @returns The resolution from the previous filter or root resolution
|
|
627
|
-
*/
|
|
628
|
-
_findFilterResolution(rootResolution) {
|
|
629
|
-
let currentIndex = this._filterStackIndex - 1;
|
|
630
|
-
while (currentIndex > 0 && this._filterStack[currentIndex].skip) {
|
|
631
|
-
--currentIndex;
|
|
632
|
-
}
|
|
633
|
-
return currentIndex > 0 && this._filterStack[currentIndex].inputTexture ? this._filterStack[currentIndex].inputTexture.source._resolution : rootResolution;
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Finds the offset from the previous non-skipped filter in the stack.
|
|
637
|
-
* @returns The offset coordinates from the previous filter
|
|
638
|
-
*/
|
|
639
|
-
_findPreviousFilterOffset() {
|
|
640
|
-
let offsetX = 0;
|
|
641
|
-
let offsetY = 0;
|
|
642
|
-
let lastIndex = this._filterStackIndex;
|
|
643
|
-
while (lastIndex > 0) {
|
|
644
|
-
lastIndex--;
|
|
645
|
-
const prevFilterData = this._filterStack[lastIndex];
|
|
646
|
-
if (!prevFilterData.skip) {
|
|
647
|
-
offsetX = prevFilterData.bounds.minX;
|
|
648
|
-
offsetY = prevFilterData.bounds.minY;
|
|
649
|
-
break;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return { x: offsetX, y: offsetY };
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Calculates the filter area bounds based on the instruction type.
|
|
656
|
-
* @param instruction - The filter instruction
|
|
657
|
-
* @param bounds - The bounds object to populate
|
|
658
|
-
*/
|
|
659
|
-
_calculateFilterArea(instruction, bounds) {
|
|
660
|
-
if (instruction.renderables) {
|
|
661
|
-
getGlobalRenderableBounds(instruction.renderables, bounds);
|
|
662
|
-
} else if (instruction.filterEffect.filterArea) {
|
|
663
|
-
bounds.clear();
|
|
664
|
-
bounds.addRect(instruction.filterEffect.filterArea);
|
|
665
|
-
bounds.applyMatrix(instruction.container.worldTransform);
|
|
666
|
-
} else {
|
|
667
|
-
instruction.container.getFastGlobalBounds(true, bounds);
|
|
668
|
-
}
|
|
669
|
-
if (instruction.container) {
|
|
670
|
-
const renderGroup = instruction.container.renderGroup || instruction.container.parentRenderGroup;
|
|
671
|
-
const filterFrameTransform = renderGroup.cacheToLocalTransform;
|
|
672
|
-
if (filterFrameTransform) {
|
|
673
|
-
bounds.applyMatrix(filterFrameTransform);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
_applyFiltersToTexture(filterData, clear) {
|
|
678
|
-
const inputTexture = filterData.inputTexture;
|
|
679
|
-
const bounds = filterData.bounds;
|
|
680
|
-
const filters = filterData.filters;
|
|
681
|
-
this._globalFilterBindGroup.setResource(inputTexture.source.style, 2);
|
|
682
|
-
this._globalFilterBindGroup.setResource(filterData.backTexture.source, 3);
|
|
683
|
-
if (filters.length === 1) {
|
|
684
|
-
filters[0].apply(this, inputTexture, filterData.outputRenderSurface, clear);
|
|
685
|
-
} else {
|
|
686
|
-
let flip = filterData.inputTexture;
|
|
687
|
-
const tempTexture = TexturePool.getOptimalTexture(
|
|
688
|
-
bounds.width,
|
|
689
|
-
bounds.height,
|
|
690
|
-
flip.source._resolution,
|
|
691
|
-
false
|
|
692
|
-
);
|
|
693
|
-
let flop = tempTexture;
|
|
694
|
-
let i = 0;
|
|
695
|
-
for (i = 0; i < filters.length - 1; ++i) {
|
|
696
|
-
const filter = filters[i];
|
|
697
|
-
filter.apply(this, flip, flop, true);
|
|
698
|
-
const t = flip;
|
|
699
|
-
flip = flop;
|
|
700
|
-
flop = t;
|
|
701
|
-
}
|
|
702
|
-
filters[i].apply(this, flip, filterData.outputRenderSurface, clear);
|
|
703
|
-
TexturePool.returnTexture(tempTexture);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
_calculateFilterBounds(filterData, viewPort, rootAntialias, rootResolution, paddingMultiplier) {
|
|
707
|
-
const renderer = this.renderer;
|
|
708
|
-
const bounds = filterData.bounds;
|
|
709
|
-
const filters = filterData.filters;
|
|
710
|
-
let resolution = Infinity;
|
|
711
|
-
let padding = 0;
|
|
712
|
-
let antialias = true;
|
|
713
|
-
let blendRequired = false;
|
|
714
|
-
let enabled = false;
|
|
715
|
-
let clipToViewport = true;
|
|
716
|
-
for (let i = 0; i < filters.length; i++) {
|
|
717
|
-
const filter = filters[i];
|
|
718
|
-
resolution = Math.min(resolution, filter.resolution === "inherit" ? rootResolution : filter.resolution);
|
|
719
|
-
padding += filter.padding;
|
|
720
|
-
if (filter.antialias === "off") {
|
|
721
|
-
antialias = false;
|
|
722
|
-
} else if (filter.antialias === "inherit") {
|
|
723
|
-
antialias && (antialias = rootAntialias);
|
|
724
|
-
}
|
|
725
|
-
if (!filter.clipToViewport) {
|
|
726
|
-
clipToViewport = false;
|
|
727
|
-
}
|
|
728
|
-
const isCompatible = !!(filter.compatibleRenderers & renderer.type);
|
|
729
|
-
if (!isCompatible) {
|
|
730
|
-
enabled = false;
|
|
731
|
-
break;
|
|
732
|
-
}
|
|
733
|
-
if (filter.blendRequired && !(renderer.backBuffer?.useBackBuffer ?? true)) {
|
|
734
|
-
warn("Blend filter requires backBuffer on WebGL renderer to be enabled. Set `useBackBuffer: true` in the renderer options.");
|
|
735
|
-
enabled = false;
|
|
736
|
-
break;
|
|
737
|
-
}
|
|
738
|
-
enabled = filter.enabled || enabled;
|
|
739
|
-
blendRequired || (blendRequired = filter.blendRequired);
|
|
740
|
-
}
|
|
741
|
-
if (!enabled) {
|
|
742
|
-
filterData.skip = true;
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
if (clipToViewport) {
|
|
746
|
-
bounds.fitBounds(0, viewPort.width / rootResolution, 0, viewPort.height / rootResolution);
|
|
747
|
-
}
|
|
748
|
-
bounds.scale(resolution).ceil().scale(1 / resolution).pad((padding | 0) * paddingMultiplier);
|
|
749
|
-
if (!bounds.isPositive) {
|
|
750
|
-
filterData.skip = true;
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
filterData.antialias = antialias;
|
|
754
|
-
filterData.resolution = resolution;
|
|
755
|
-
filterData.blendRequired = blendRequired;
|
|
756
|
-
}
|
|
757
|
-
_popFilterData() {
|
|
758
|
-
this._filterStackIndex--;
|
|
759
|
-
return this._filterStack[this._filterStackIndex];
|
|
760
|
-
}
|
|
761
|
-
_getPreviousFilterData() {
|
|
762
|
-
let previousFilterData;
|
|
763
|
-
let index = this._filterStackIndex - 1;
|
|
764
|
-
while (index > 1) {
|
|
765
|
-
index--;
|
|
766
|
-
previousFilterData = this._filterStack[index];
|
|
767
|
-
if (!previousFilterData.skip) {
|
|
768
|
-
break;
|
|
769
|
-
}
|
|
770
|
-
}
|
|
771
|
-
return previousFilterData;
|
|
772
|
-
}
|
|
773
|
-
_pushFilterData() {
|
|
774
|
-
let filterData = this._filterStack[this._filterStackIndex];
|
|
775
|
-
if (!filterData) {
|
|
776
|
-
filterData = this._filterStack[this._filterStackIndex] = new FilterData();
|
|
777
|
-
}
|
|
778
|
-
this._filterStackIndex++;
|
|
779
|
-
return filterData;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
/** @ignore */
|
|
783
|
-
FilterSystem.extension = {
|
|
784
|
-
type: [
|
|
785
|
-
ExtensionType.WebGLSystem,
|
|
786
|
-
ExtensionType.WebGPUSystem
|
|
787
|
-
],
|
|
788
|
-
name: "filter"
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
class Graphics extends ViewContainer {
|
|
792
|
-
/**
|
|
793
|
-
* Creates a new Graphics object.
|
|
794
|
-
* @param options - Options for the Graphics.
|
|
795
|
-
*/
|
|
796
|
-
constructor(options) {
|
|
797
|
-
if (options instanceof GraphicsContext) {
|
|
798
|
-
options = { context: options };
|
|
799
|
-
}
|
|
800
|
-
const { context, roundPixels, ...rest } = options || {};
|
|
801
|
-
super({
|
|
802
|
-
label: "Graphics",
|
|
803
|
-
...rest
|
|
804
|
-
});
|
|
805
|
-
/** @internal */
|
|
806
|
-
this.renderPipeId = "graphics";
|
|
807
|
-
if (!context) {
|
|
808
|
-
this._context = this._ownedContext = new GraphicsContext();
|
|
809
|
-
} else {
|
|
810
|
-
this._context = context;
|
|
811
|
-
}
|
|
812
|
-
this._context.on("update", this.onViewUpdate, this);
|
|
813
|
-
this.didViewUpdate = true;
|
|
814
|
-
this.allowChildren = false;
|
|
815
|
-
this.roundPixels = roundPixels ?? false;
|
|
816
|
-
}
|
|
817
|
-
set context(context) {
|
|
818
|
-
if (context === this._context)
|
|
819
|
-
return;
|
|
820
|
-
this._context.off("update", this.onViewUpdate, this);
|
|
821
|
-
this._context = context;
|
|
822
|
-
this._context.on("update", this.onViewUpdate, this);
|
|
823
|
-
this.onViewUpdate();
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* The underlying graphics context used for drawing operations.
|
|
827
|
-
* Controls how shapes and paths are rendered.
|
|
828
|
-
* @example
|
|
829
|
-
* ```ts
|
|
830
|
-
* // Create a shared context
|
|
831
|
-
* const sharedContext = new GraphicsContext();
|
|
832
|
-
*
|
|
833
|
-
* // Create graphics objects sharing the same context
|
|
834
|
-
* const graphics1 = new Graphics();
|
|
835
|
-
* const graphics2 = new Graphics();
|
|
836
|
-
*
|
|
837
|
-
* // Assign shared context
|
|
838
|
-
* graphics1.context = sharedContext;
|
|
839
|
-
* graphics2.context = sharedContext;
|
|
840
|
-
*
|
|
841
|
-
* // Both graphics will show the same shapes
|
|
842
|
-
* sharedContext
|
|
843
|
-
* .rect(0, 0, 100, 100)
|
|
844
|
-
* .fill({ color: 0xff0000 });
|
|
845
|
-
* ```
|
|
846
|
-
* @see {@link GraphicsContext} For drawing operations
|
|
847
|
-
* @see {@link GraphicsOptions} For context configuration
|
|
848
|
-
*/
|
|
849
|
-
get context() {
|
|
850
|
-
return this._context;
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
* The local bounds of the graphics object.
|
|
854
|
-
* Returns the boundaries after all graphical operations but before any transforms.
|
|
855
|
-
* @example
|
|
856
|
-
* ```ts
|
|
857
|
-
* const graphics = new Graphics();
|
|
858
|
-
*
|
|
859
|
-
* // Draw a shape
|
|
860
|
-
* graphics
|
|
861
|
-
* .rect(0, 0, 100, 100)
|
|
862
|
-
* .fill({ color: 0xff0000 });
|
|
863
|
-
*
|
|
864
|
-
* // Get bounds information
|
|
865
|
-
* const bounds = graphics.bounds;
|
|
866
|
-
* console.log(bounds.width); // 100
|
|
867
|
-
* console.log(bounds.height); // 100
|
|
868
|
-
* ```
|
|
869
|
-
* @readonly
|
|
870
|
-
* @see {@link Bounds} For bounds operations
|
|
871
|
-
* @see {@link Container#getBounds} For transformed bounds
|
|
872
|
-
*/
|
|
873
|
-
get bounds() {
|
|
874
|
-
return this._context.bounds;
|
|
875
|
-
}
|
|
876
|
-
/**
|
|
877
|
-
* Graphics objects do not need to update their bounds as the context handles this.
|
|
878
|
-
* @private
|
|
879
|
-
*/
|
|
880
|
-
updateBounds() {
|
|
881
|
-
}
|
|
882
|
-
/**
|
|
883
|
-
* Checks if the object contains the given point.
|
|
884
|
-
* Returns true if the point lies within the Graphics object's rendered area.
|
|
885
|
-
* @example
|
|
886
|
-
* ```ts
|
|
887
|
-
* const graphics = new Graphics();
|
|
888
|
-
*
|
|
889
|
-
* // Draw a shape
|
|
890
|
-
* graphics
|
|
891
|
-
* .rect(0, 0, 100, 100)
|
|
892
|
-
* .fill({ color: 0xff0000 });
|
|
893
|
-
*
|
|
894
|
-
* // Check point intersection
|
|
895
|
-
* if (graphics.containsPoint({ x: 50, y: 50 })) {
|
|
896
|
-
* console.log('Point is inside rectangle!');
|
|
897
|
-
* }
|
|
898
|
-
* ```
|
|
899
|
-
* @param point - The point to check in local coordinates
|
|
900
|
-
* @returns True if the point is inside the Graphics object
|
|
901
|
-
* @see {@link Graphics#bounds} For bounding box checks
|
|
902
|
-
* @see {@link PointData} For point data structure
|
|
903
|
-
*/
|
|
904
|
-
containsPoint(point) {
|
|
905
|
-
return this._context.containsPoint(point);
|
|
906
|
-
}
|
|
907
|
-
/**
|
|
908
|
-
* Destroys this graphics renderable and optionally its context.
|
|
909
|
-
* @param options - Options parameter. A boolean will act as if all options
|
|
910
|
-
*
|
|
911
|
-
* If the context was created by this graphics and `destroy(false)` or `destroy()` is called
|
|
912
|
-
* then the context will still be destroyed.
|
|
913
|
-
*
|
|
914
|
-
* If you want to explicitly not destroy this context that this graphics created,
|
|
915
|
-
* then you should pass destroy({ context: false })
|
|
916
|
-
*
|
|
917
|
-
* If the context was passed in as an argument to the constructor then it will not be destroyed
|
|
918
|
-
* @example
|
|
919
|
-
* ```ts
|
|
920
|
-
* // Destroy the graphics and its context
|
|
921
|
-
* graphics.destroy();
|
|
922
|
-
* graphics.destroy(true);
|
|
923
|
-
* graphics.destroy({ context: true, texture: true, textureSource: true });
|
|
924
|
-
* ```
|
|
925
|
-
*/
|
|
926
|
-
destroy(options) {
|
|
927
|
-
if (this._ownedContext && !options) {
|
|
928
|
-
this._ownedContext.destroy(options);
|
|
929
|
-
} else if (options === true || options?.context === true) {
|
|
930
|
-
this._context.destroy(options);
|
|
931
|
-
}
|
|
932
|
-
this._ownedContext = null;
|
|
933
|
-
this._context = null;
|
|
934
|
-
super.destroy(options);
|
|
935
|
-
}
|
|
936
|
-
_callContextMethod(method, args) {
|
|
937
|
-
this.context[method](...args);
|
|
938
|
-
return this;
|
|
939
|
-
}
|
|
940
|
-
// --------------------------------------- GraphicsContext methods ---------------------------------------
|
|
941
|
-
/**
|
|
942
|
-
* Sets the current fill style of the graphics context.
|
|
943
|
-
* The fill style can be a color, gradient, pattern, or a complex style object.
|
|
944
|
-
* @example
|
|
945
|
-
* ```ts
|
|
946
|
-
* const graphics = new Graphics();
|
|
947
|
-
*
|
|
948
|
-
* // Basic color fill
|
|
949
|
-
* graphics
|
|
950
|
-
* .setFillStyle({ color: 0xff0000 }) // Red fill
|
|
951
|
-
* .rect(0, 0, 100, 100)
|
|
952
|
-
* .fill();
|
|
953
|
-
*
|
|
954
|
-
* // Gradient fill
|
|
955
|
-
* const gradient = new FillGradient({
|
|
956
|
-
* end: { x: 1, y: 0 },
|
|
957
|
-
* colorStops: [
|
|
958
|
-
* { offset: 0, color: 0xff0000 }, // Red at start
|
|
959
|
-
* { offset: 0.5, color: 0x00ff00 }, // Green at middle
|
|
960
|
-
* { offset: 1, color: 0x0000ff }, // Blue at end
|
|
961
|
-
* ],
|
|
962
|
-
* });
|
|
963
|
-
*
|
|
964
|
-
* graphics
|
|
965
|
-
* .setFillStyle(gradient)
|
|
966
|
-
* .circle(100, 100, 50)
|
|
967
|
-
* .fill();
|
|
968
|
-
*
|
|
969
|
-
* // Pattern fill
|
|
970
|
-
* const pattern = new FillPattern(texture);
|
|
971
|
-
* graphics
|
|
972
|
-
* .setFillStyle({
|
|
973
|
-
* fill: pattern,
|
|
974
|
-
* alpha: 0.5
|
|
975
|
-
* })
|
|
976
|
-
* .rect(0, 0, 200, 200)
|
|
977
|
-
* .fill();
|
|
978
|
-
* ```
|
|
979
|
-
* @param {FillInput} args - The fill style to apply
|
|
980
|
-
* @returns The Graphics instance for chaining
|
|
981
|
-
* @see {@link FillStyle} For fill style options
|
|
982
|
-
* @see {@link FillGradient} For gradient fills
|
|
983
|
-
* @see {@link FillPattern} For pattern fills
|
|
984
|
-
*/
|
|
985
|
-
setFillStyle(...args) {
|
|
986
|
-
return this._callContextMethod("setFillStyle", args);
|
|
987
|
-
}
|
|
988
|
-
/**
|
|
989
|
-
* Sets the current stroke style of the graphics context.
|
|
990
|
-
* Similar to fill styles, stroke styles can encompass colors, gradients, patterns, or more detailed configurations.
|
|
991
|
-
* @example
|
|
992
|
-
* ```ts
|
|
993
|
-
* const graphics = new Graphics();
|
|
994
|
-
*
|
|
995
|
-
* // Basic color stroke
|
|
996
|
-
* graphics
|
|
997
|
-
* .setStrokeStyle({
|
|
998
|
-
* width: 2,
|
|
999
|
-
* color: 0x000000
|
|
1000
|
-
* })
|
|
1001
|
-
* .rect(0, 0, 100, 100)
|
|
1002
|
-
* .stroke();
|
|
1003
|
-
*
|
|
1004
|
-
* // Complex stroke style
|
|
1005
|
-
* graphics
|
|
1006
|
-
* .setStrokeStyle({
|
|
1007
|
-
* width: 4,
|
|
1008
|
-
* color: 0xff0000,
|
|
1009
|
-
* alpha: 0.5,
|
|
1010
|
-
* join: 'round',
|
|
1011
|
-
* cap: 'round',
|
|
1012
|
-
* alignment: 0.5
|
|
1013
|
-
* })
|
|
1014
|
-
* .circle(100, 100, 50)
|
|
1015
|
-
* .stroke();
|
|
1016
|
-
*
|
|
1017
|
-
* // Gradient stroke
|
|
1018
|
-
* const gradient = new FillGradient({
|
|
1019
|
-
* end: { x: 1, y: 0 },
|
|
1020
|
-
* colorStops: [
|
|
1021
|
-
* { offset: 0, color: 0xff0000 }, // Red at start
|
|
1022
|
-
* { offset: 0.5, color: 0x00ff00 }, // Green at middle
|
|
1023
|
-
* { offset: 1, color: 0x0000ff }, // Blue at end
|
|
1024
|
-
* ],
|
|
1025
|
-
* });
|
|
1026
|
-
*
|
|
1027
|
-
* graphics
|
|
1028
|
-
* .setStrokeStyle({
|
|
1029
|
-
* width: 10,
|
|
1030
|
-
* fill: gradient
|
|
1031
|
-
* })
|
|
1032
|
-
* .poly([0,0, 100,50, 0,100])
|
|
1033
|
-
* .stroke();
|
|
1034
|
-
* ```
|
|
1035
|
-
* @param {StrokeInput} args - The stroke style to apply
|
|
1036
|
-
* @returns The Graphics instance for chaining
|
|
1037
|
-
* @see {@link StrokeStyle} For stroke style options
|
|
1038
|
-
* @see {@link FillGradient} For gradient strokes
|
|
1039
|
-
* @see {@link FillPattern} For pattern strokes
|
|
1040
|
-
*/
|
|
1041
|
-
setStrokeStyle(...args) {
|
|
1042
|
-
return this._callContextMethod("setStrokeStyle", args);
|
|
1043
|
-
}
|
|
1044
|
-
fill(...args) {
|
|
1045
|
-
return this._callContextMethod("fill", args);
|
|
1046
|
-
}
|
|
1047
|
-
/**
|
|
1048
|
-
* Strokes the current path with the current stroke style or specified style.
|
|
1049
|
-
* Outlines the shape using the stroke settings.
|
|
1050
|
-
* @example
|
|
1051
|
-
* ```ts
|
|
1052
|
-
* const graphics = new Graphics();
|
|
1053
|
-
*
|
|
1054
|
-
* // Stroke with direct color
|
|
1055
|
-
* graphics
|
|
1056
|
-
* .circle(50, 50, 25)
|
|
1057
|
-
* .stroke({
|
|
1058
|
-
* width: 2,
|
|
1059
|
-
* color: 0xff0000
|
|
1060
|
-
* }); // 2px red stroke
|
|
1061
|
-
*
|
|
1062
|
-
* // Fill with texture
|
|
1063
|
-
* graphics
|
|
1064
|
-
* .rect(0, 0, 100, 100)
|
|
1065
|
-
* .stroke(myTexture); // Fill with texture
|
|
1066
|
-
*
|
|
1067
|
-
* // Stroke with gradient
|
|
1068
|
-
* const gradient = new FillGradient({
|
|
1069
|
-
* end: { x: 1, y: 0 },
|
|
1070
|
-
* colorStops: [
|
|
1071
|
-
* { offset: 0, color: 0xff0000 },
|
|
1072
|
-
* { offset: 0.5, color: 0x00ff00 },
|
|
1073
|
-
* { offset: 1, color: 0x0000ff },
|
|
1074
|
-
* ],
|
|
1075
|
-
* });
|
|
1076
|
-
*
|
|
1077
|
-
* graphics
|
|
1078
|
-
* .rect(0, 0, 100, 100)
|
|
1079
|
-
* .stroke({
|
|
1080
|
-
* width: 4,
|
|
1081
|
-
* fill: gradient,
|
|
1082
|
-
* alignment: 0.5,
|
|
1083
|
-
* join: 'round'
|
|
1084
|
-
* });
|
|
1085
|
-
* ```
|
|
1086
|
-
* @param {StrokeStyle} args - Optional stroke style to apply. Can be:
|
|
1087
|
-
* - A stroke style object with width, color, etc.
|
|
1088
|
-
* - A gradient
|
|
1089
|
-
* - A pattern
|
|
1090
|
-
* If omitted, uses current stroke style.
|
|
1091
|
-
* @returns The Graphics instance for chaining
|
|
1092
|
-
* @see {@link StrokeStyle} For stroke style options
|
|
1093
|
-
* @see {@link FillGradient} For gradient strokes
|
|
1094
|
-
* @see {@link setStrokeStyle} For setting default stroke style
|
|
1095
|
-
*/
|
|
1096
|
-
stroke(...args) {
|
|
1097
|
-
return this._callContextMethod("stroke", args);
|
|
1098
|
-
}
|
|
1099
|
-
texture(...args) {
|
|
1100
|
-
return this._callContextMethod("texture", args);
|
|
1101
|
-
}
|
|
1102
|
-
/**
|
|
1103
|
-
* Resets the current path. Any previous path and its commands are discarded and a new path is
|
|
1104
|
-
* started. This is typically called before beginning a new shape or series of drawing commands.
|
|
1105
|
-
* @example
|
|
1106
|
-
* ```ts
|
|
1107
|
-
* const graphics = new Graphics();
|
|
1108
|
-
* graphics
|
|
1109
|
-
* .circle(150, 150, 50)
|
|
1110
|
-
* .fill({ color: 0x00ff00 })
|
|
1111
|
-
* .beginPath() // Starts a new path
|
|
1112
|
-
* .circle(250, 150, 50)
|
|
1113
|
-
* .fill({ color: 0x0000ff });
|
|
1114
|
-
* ```
|
|
1115
|
-
* @returns The Graphics instance for chaining
|
|
1116
|
-
* @see {@link Graphics#moveTo} For starting a new subpath
|
|
1117
|
-
* @see {@link Graphics#closePath} For closing the current path
|
|
1118
|
-
*/
|
|
1119
|
-
beginPath() {
|
|
1120
|
-
return this._callContextMethod("beginPath", []);
|
|
1121
|
-
}
|
|
1122
|
-
/**
|
|
1123
|
-
* Applies a cutout to the last drawn shape. This is used to create holes or complex shapes by
|
|
1124
|
-
* subtracting a path from the previously drawn path.
|
|
1125
|
-
*
|
|
1126
|
-
* If a hole is not completely in a shape, it will fail to cut correctly.
|
|
1127
|
-
* @example
|
|
1128
|
-
* ```ts
|
|
1129
|
-
* const graphics = new Graphics();
|
|
1130
|
-
*
|
|
1131
|
-
* // Draw outer circle
|
|
1132
|
-
* graphics
|
|
1133
|
-
* .circle(100, 100, 50)
|
|
1134
|
-
* .fill({ color: 0xff0000 });
|
|
1135
|
-
* .circle(100, 100, 25) // Inner circle
|
|
1136
|
-
* .cut() // Cuts out the inner circle from the outer circle
|
|
1137
|
-
* ```
|
|
1138
|
-
*/
|
|
1139
|
-
cut() {
|
|
1140
|
-
return this._callContextMethod("cut", []);
|
|
1141
|
-
}
|
|
1142
|
-
arc(...args) {
|
|
1143
|
-
return this._callContextMethod("arc", args);
|
|
1144
|
-
}
|
|
1145
|
-
arcTo(...args) {
|
|
1146
|
-
return this._callContextMethod("arcTo", args);
|
|
1147
|
-
}
|
|
1148
|
-
arcToSvg(...args) {
|
|
1149
|
-
return this._callContextMethod("arcToSvg", args);
|
|
1150
|
-
}
|
|
1151
|
-
bezierCurveTo(...args) {
|
|
1152
|
-
return this._callContextMethod("bezierCurveTo", args);
|
|
1153
|
-
}
|
|
1154
|
-
/**
|
|
1155
|
-
* Closes the current path by drawing a straight line back to the start point.
|
|
1156
|
-
*
|
|
1157
|
-
* This is useful for completing shapes and ensuring they are properly closed for fills.
|
|
1158
|
-
* @example
|
|
1159
|
-
* ```ts
|
|
1160
|
-
* // Create a triangle with closed path
|
|
1161
|
-
* const graphics = new Graphics();
|
|
1162
|
-
* graphics
|
|
1163
|
-
* .moveTo(50, 50)
|
|
1164
|
-
* .lineTo(100, 100)
|
|
1165
|
-
* .lineTo(0, 100)
|
|
1166
|
-
* .closePath()
|
|
1167
|
-
* ```
|
|
1168
|
-
* @returns The Graphics instance for method chaining
|
|
1169
|
-
* @see {@link Graphics#beginPath} For starting a new path
|
|
1170
|
-
* @see {@link Graphics#fill} For filling closed paths
|
|
1171
|
-
* @see {@link Graphics#stroke} For stroking paths
|
|
1172
|
-
*/
|
|
1173
|
-
closePath() {
|
|
1174
|
-
return this._callContextMethod("closePath", []);
|
|
1175
|
-
}
|
|
1176
|
-
ellipse(...args) {
|
|
1177
|
-
return this._callContextMethod("ellipse", args);
|
|
1178
|
-
}
|
|
1179
|
-
circle(...args) {
|
|
1180
|
-
return this._callContextMethod("circle", args);
|
|
1181
|
-
}
|
|
1182
|
-
path(...args) {
|
|
1183
|
-
return this._callContextMethod("path", args);
|
|
1184
|
-
}
|
|
1185
|
-
lineTo(...args) {
|
|
1186
|
-
return this._callContextMethod("lineTo", args);
|
|
1187
|
-
}
|
|
1188
|
-
moveTo(...args) {
|
|
1189
|
-
return this._callContextMethod("moveTo", args);
|
|
1190
|
-
}
|
|
1191
|
-
quadraticCurveTo(...args) {
|
|
1192
|
-
return this._callContextMethod("quadraticCurveTo", args);
|
|
1193
|
-
}
|
|
1194
|
-
rect(...args) {
|
|
1195
|
-
return this._callContextMethod("rect", args);
|
|
1196
|
-
}
|
|
1197
|
-
roundRect(...args) {
|
|
1198
|
-
return this._callContextMethod("roundRect", args);
|
|
1199
|
-
}
|
|
1200
|
-
poly(...args) {
|
|
1201
|
-
return this._callContextMethod("poly", args);
|
|
1202
|
-
}
|
|
1203
|
-
regularPoly(...args) {
|
|
1204
|
-
return this._callContextMethod("regularPoly", args);
|
|
1205
|
-
}
|
|
1206
|
-
roundPoly(...args) {
|
|
1207
|
-
return this._callContextMethod("roundPoly", args);
|
|
1208
|
-
}
|
|
1209
|
-
roundShape(...args) {
|
|
1210
|
-
return this._callContextMethod("roundShape", args);
|
|
1211
|
-
}
|
|
1212
|
-
filletRect(...args) {
|
|
1213
|
-
return this._callContextMethod("filletRect", args);
|
|
1214
|
-
}
|
|
1215
|
-
chamferRect(...args) {
|
|
1216
|
-
return this._callContextMethod("chamferRect", args);
|
|
1217
|
-
}
|
|
1218
|
-
star(...args) {
|
|
1219
|
-
return this._callContextMethod("star", args);
|
|
1220
|
-
}
|
|
1221
|
-
svg(...args) {
|
|
1222
|
-
return this._callContextMethod("svg", args);
|
|
1223
|
-
}
|
|
1224
|
-
restore(...args) {
|
|
1225
|
-
return this._callContextMethod("restore", args);
|
|
1226
|
-
}
|
|
1227
|
-
/**
|
|
1228
|
-
* Saves the current graphics state onto a stack. The state includes:
|
|
1229
|
-
* - Current transformation matrix
|
|
1230
|
-
* - Current fill style
|
|
1231
|
-
* - Current stroke style
|
|
1232
|
-
* @example
|
|
1233
|
-
* ```ts
|
|
1234
|
-
* const graphics = new Graphics();
|
|
1235
|
-
*
|
|
1236
|
-
* // Save state before complex operations
|
|
1237
|
-
* graphics.save();
|
|
1238
|
-
*
|
|
1239
|
-
* // Create transformed and styled shape
|
|
1240
|
-
* graphics
|
|
1241
|
-
* .translateTransform(100, 100)
|
|
1242
|
-
* .rotateTransform(Math.PI / 4)
|
|
1243
|
-
* .setFillStyle({
|
|
1244
|
-
* color: 0xff0000,
|
|
1245
|
-
* alpha: 0.5
|
|
1246
|
-
* })
|
|
1247
|
-
* .rect(-25, -25, 50, 50)
|
|
1248
|
-
* .fill();
|
|
1249
|
-
*
|
|
1250
|
-
* // Restore to original state
|
|
1251
|
-
* graphics.restore();
|
|
1252
|
-
*
|
|
1253
|
-
* // Continue drawing with previous state
|
|
1254
|
-
* graphics
|
|
1255
|
-
* .circle(50, 50, 25)
|
|
1256
|
-
* .fill();
|
|
1257
|
-
* ```
|
|
1258
|
-
* @returns The Graphics instance for method chaining
|
|
1259
|
-
* @see {@link Graphics#restore} For restoring the saved state
|
|
1260
|
-
* @see {@link Graphics#setTransform} For setting transformations
|
|
1261
|
-
*/
|
|
1262
|
-
save() {
|
|
1263
|
-
return this._callContextMethod("save", []);
|
|
1264
|
-
}
|
|
1265
|
-
/**
|
|
1266
|
-
* Returns the current transformation matrix of the graphics context.
|
|
1267
|
-
* This matrix represents all accumulated transformations including translate, scale, and rotate.
|
|
1268
|
-
* @example
|
|
1269
|
-
* ```ts
|
|
1270
|
-
* const graphics = new Graphics();
|
|
1271
|
-
*
|
|
1272
|
-
* // Apply some transformations
|
|
1273
|
-
* graphics
|
|
1274
|
-
* .translateTransform(100, 100)
|
|
1275
|
-
* .rotateTransform(Math.PI / 4);
|
|
1276
|
-
*
|
|
1277
|
-
* // Get the current transform matrix
|
|
1278
|
-
* const matrix = graphics.getTransform();
|
|
1279
|
-
* console.log(matrix.tx, matrix.ty); // 100, 100
|
|
1280
|
-
*
|
|
1281
|
-
* // Use the matrix for other operations
|
|
1282
|
-
* graphics
|
|
1283
|
-
* .setTransform(matrix)
|
|
1284
|
-
* .circle(0, 0, 50)
|
|
1285
|
-
* .fill({ color: 0xff0000 });
|
|
1286
|
-
* ```
|
|
1287
|
-
* @returns The current transformation matrix.
|
|
1288
|
-
* @see {@link Graphics#setTransform} For setting the transform matrix
|
|
1289
|
-
* @see {@link Matrix} For matrix operations
|
|
1290
|
-
*/
|
|
1291
|
-
getTransform() {
|
|
1292
|
-
return this.context.getTransform();
|
|
1293
|
-
}
|
|
1294
|
-
/**
|
|
1295
|
-
* Resets the current transformation matrix to the identity matrix, effectively removing
|
|
1296
|
-
* any transformations (rotation, scaling, translation) previously applied.
|
|
1297
|
-
* @example
|
|
1298
|
-
* ```ts
|
|
1299
|
-
* const graphics = new Graphics();
|
|
1300
|
-
*
|
|
1301
|
-
* // Apply transformations
|
|
1302
|
-
* graphics
|
|
1303
|
-
* .translateTransform(100, 100)
|
|
1304
|
-
* .scaleTransform(2, 2)
|
|
1305
|
-
* .circle(0, 0, 25)
|
|
1306
|
-
* .fill({ color: 0xff0000 });
|
|
1307
|
-
* // Reset transform to default state
|
|
1308
|
-
* graphics
|
|
1309
|
-
* .resetTransform()
|
|
1310
|
-
* .circle(50, 50, 25) // Will draw at actual coordinates
|
|
1311
|
-
* .fill({ color: 0x00ff00 });
|
|
1312
|
-
* ```
|
|
1313
|
-
* @returns The Graphics instance for method chaining
|
|
1314
|
-
* @see {@link Graphics#getTransform} For getting the current transform
|
|
1315
|
-
* @see {@link Graphics#setTransform} For setting a specific transform
|
|
1316
|
-
* @see {@link Graphics#save} For saving the current transform state
|
|
1317
|
-
* @see {@link Graphics#restore} For restoring a previous transform state
|
|
1318
|
-
*/
|
|
1319
|
-
resetTransform() {
|
|
1320
|
-
return this._callContextMethod("resetTransform", []);
|
|
1321
|
-
}
|
|
1322
|
-
rotateTransform(...args) {
|
|
1323
|
-
return this._callContextMethod("rotate", args);
|
|
1324
|
-
}
|
|
1325
|
-
scaleTransform(...args) {
|
|
1326
|
-
return this._callContextMethod("scale", args);
|
|
1327
|
-
}
|
|
1328
|
-
setTransform(...args) {
|
|
1329
|
-
return this._callContextMethod("setTransform", args);
|
|
1330
|
-
}
|
|
1331
|
-
transform(...args) {
|
|
1332
|
-
return this._callContextMethod("transform", args);
|
|
1333
|
-
}
|
|
1334
|
-
translateTransform(...args) {
|
|
1335
|
-
return this._callContextMethod("translate", args);
|
|
1336
|
-
}
|
|
1337
|
-
/**
|
|
1338
|
-
* Clears all drawing commands from the graphics context, effectively resetting it.
|
|
1339
|
-
* This includes clearing the current path, fill style, stroke style, and transformations.
|
|
1340
|
-
*
|
|
1341
|
-
* > [!NOTE] Graphics objects are not designed to be continuously cleared and redrawn.
|
|
1342
|
-
* > Instead, they are intended to be used for static or semi-static graphics that
|
|
1343
|
-
* > can be redrawn as needed. Frequent clearing and redrawing may lead to performance issues.
|
|
1344
|
-
* @example
|
|
1345
|
-
* ```ts
|
|
1346
|
-
* const graphics = new Graphics();
|
|
1347
|
-
*
|
|
1348
|
-
* // Draw some shapes
|
|
1349
|
-
* graphics
|
|
1350
|
-
* .circle(100, 100, 50)
|
|
1351
|
-
* .fill({ color: 0xff0000 })
|
|
1352
|
-
* .rect(200, 100, 100, 50)
|
|
1353
|
-
* .fill({ color: 0x00ff00 });
|
|
1354
|
-
*
|
|
1355
|
-
* // Clear all graphics
|
|
1356
|
-
* graphics.clear();
|
|
1357
|
-
*
|
|
1358
|
-
* // Start fresh with new shapes
|
|
1359
|
-
* graphics
|
|
1360
|
-
* .circle(150, 150, 30)
|
|
1361
|
-
* .fill({ color: 0x0000ff });
|
|
1362
|
-
* ```
|
|
1363
|
-
* @returns The Graphics instance for method chaining
|
|
1364
|
-
* @see {@link Graphics#beginPath} For starting a new path without clearing styles
|
|
1365
|
-
* @see {@link Graphics#save} For saving the current state
|
|
1366
|
-
* @see {@link Graphics#restore} For restoring a previous state
|
|
1367
|
-
*/
|
|
1368
|
-
clear() {
|
|
1369
|
-
return this._callContextMethod("clear", []);
|
|
1370
|
-
}
|
|
1371
|
-
/**
|
|
1372
|
-
* Gets or sets the current fill style for the graphics context. The fill style determines
|
|
1373
|
-
* how shapes are filled when using the fill() method.
|
|
1374
|
-
* @example
|
|
1375
|
-
* ```ts
|
|
1376
|
-
* const graphics = new Graphics();
|
|
1377
|
-
*
|
|
1378
|
-
* // Basic color fill
|
|
1379
|
-
* graphics.fillStyle = {
|
|
1380
|
-
* color: 0xff0000, // Red
|
|
1381
|
-
* alpha: 1
|
|
1382
|
-
* };
|
|
1383
|
-
*
|
|
1384
|
-
* // Using gradients
|
|
1385
|
-
* const gradient = new FillGradient({
|
|
1386
|
-
* end: { x: 0, y: 1 }, // Vertical gradient
|
|
1387
|
-
* stops: [
|
|
1388
|
-
* { offset: 0, color: 0xff0000, alpha: 1 }, // Start color
|
|
1389
|
-
* { offset: 1, color: 0x0000ff, alpha: 1 } // End color
|
|
1390
|
-
* ]
|
|
1391
|
-
* });
|
|
1392
|
-
*
|
|
1393
|
-
* graphics.fillStyle = {
|
|
1394
|
-
* fill: gradient,
|
|
1395
|
-
* alpha: 0.8
|
|
1396
|
-
* };
|
|
1397
|
-
*
|
|
1398
|
-
* // Using patterns
|
|
1399
|
-
* graphics.fillStyle = {
|
|
1400
|
-
* texture: myTexture,
|
|
1401
|
-
* alpha: 1,
|
|
1402
|
-
* matrix: new Matrix()
|
|
1403
|
-
* .scale(0.5, 0.5)
|
|
1404
|
-
* .rotate(Math.PI / 4)
|
|
1405
|
-
* };
|
|
1406
|
-
* ```
|
|
1407
|
-
* @type {ConvertedFillStyle}
|
|
1408
|
-
* @see {@link FillStyle} For all available fill style options
|
|
1409
|
-
* @see {@link FillGradient} For creating gradient fills
|
|
1410
|
-
* @see {@link Graphics#fill} For applying the fill to paths
|
|
1411
|
-
*/
|
|
1412
|
-
get fillStyle() {
|
|
1413
|
-
return this._context.fillStyle;
|
|
1414
|
-
}
|
|
1415
|
-
set fillStyle(value) {
|
|
1416
|
-
this._context.fillStyle = value;
|
|
1417
|
-
}
|
|
1418
|
-
/**
|
|
1419
|
-
* Gets or sets the current stroke style for the graphics context. The stroke style determines
|
|
1420
|
-
* how paths are outlined when using the stroke() method.
|
|
1421
|
-
* @example
|
|
1422
|
-
* ```ts
|
|
1423
|
-
* const graphics = new Graphics();
|
|
1424
|
-
*
|
|
1425
|
-
* // Basic stroke style
|
|
1426
|
-
* graphics.strokeStyle = {
|
|
1427
|
-
* width: 2,
|
|
1428
|
-
* color: 0xff0000,
|
|
1429
|
-
* alpha: 1
|
|
1430
|
-
* };
|
|
1431
|
-
*
|
|
1432
|
-
* // Using with gradients
|
|
1433
|
-
* const gradient = new FillGradient({
|
|
1434
|
-
* end: { x: 0, y: 1 },
|
|
1435
|
-
* stops: [
|
|
1436
|
-
* { offset: 0, color: 0xff0000, alpha: 1 },
|
|
1437
|
-
* { offset: 1, color: 0x0000ff, alpha: 1 }
|
|
1438
|
-
* ]
|
|
1439
|
-
* });
|
|
1440
|
-
*
|
|
1441
|
-
* graphics.strokeStyle = {
|
|
1442
|
-
* width: 4,
|
|
1443
|
-
* fill: gradient,
|
|
1444
|
-
* alignment: 0.5,
|
|
1445
|
-
* join: 'round',
|
|
1446
|
-
* cap: 'round'
|
|
1447
|
-
* };
|
|
1448
|
-
*
|
|
1449
|
-
* // Complex stroke settings
|
|
1450
|
-
* graphics.strokeStyle = {
|
|
1451
|
-
* width: 6,
|
|
1452
|
-
* color: 0x00ff00,
|
|
1453
|
-
* alpha: 0.5,
|
|
1454
|
-
* join: 'miter',
|
|
1455
|
-
* miterLimit: 10,
|
|
1456
|
-
* };
|
|
1457
|
-
* ```
|
|
1458
|
-
* @see {@link StrokeStyle} For all available stroke style options
|
|
1459
|
-
* @see {@link Graphics#stroke} For applying the stroke to paths
|
|
1460
|
-
*/
|
|
1461
|
-
get strokeStyle() {
|
|
1462
|
-
return this._context.strokeStyle;
|
|
1463
|
-
}
|
|
1464
|
-
set strokeStyle(value) {
|
|
1465
|
-
this._context.strokeStyle = value;
|
|
1466
|
-
}
|
|
1467
|
-
/**
|
|
1468
|
-
* Creates a new Graphics object that copies the current graphics content.
|
|
1469
|
-
* The clone can either share the same context (shallow clone) or have its own independent
|
|
1470
|
-
* context (deep clone).
|
|
1471
|
-
* @example
|
|
1472
|
-
* ```ts
|
|
1473
|
-
* const graphics = new Graphics();
|
|
1474
|
-
*
|
|
1475
|
-
* // Create original graphics content
|
|
1476
|
-
* graphics
|
|
1477
|
-
* .circle(100, 100, 50)
|
|
1478
|
-
* .fill({ color: 0xff0000 });
|
|
1479
|
-
*
|
|
1480
|
-
* // Create a shallow clone (shared context)
|
|
1481
|
-
* const shallowClone = graphics.clone();
|
|
1482
|
-
*
|
|
1483
|
-
* // Changes to original affect the clone
|
|
1484
|
-
* graphics
|
|
1485
|
-
* .circle(200, 100, 30)
|
|
1486
|
-
* .fill({ color: 0x00ff00 });
|
|
1487
|
-
*
|
|
1488
|
-
* // Create a deep clone (independent context)
|
|
1489
|
-
* const deepClone = graphics.clone(true);
|
|
1490
|
-
*
|
|
1491
|
-
* // Modify deep clone independently
|
|
1492
|
-
* deepClone
|
|
1493
|
-
* .translateTransform(100, 100)
|
|
1494
|
-
* .circle(0, 0, 40)
|
|
1495
|
-
* .fill({ color: 0x0000ff });
|
|
1496
|
-
* ```
|
|
1497
|
-
* @param deep - Whether to create a deep clone of the graphics object.
|
|
1498
|
-
* If false (default), the context will be shared between objects.
|
|
1499
|
-
* If true, creates an independent copy of the context.
|
|
1500
|
-
* @returns A new Graphics instance with either shared or copied context
|
|
1501
|
-
* @see {@link Graphics#context} For accessing the underlying graphics context
|
|
1502
|
-
* @see {@link GraphicsContext} For understanding the shared context behavior
|
|
1503
|
-
*/
|
|
1504
|
-
clone(deep = false) {
|
|
1505
|
-
if (deep) {
|
|
1506
|
-
return new Graphics(this._context.clone());
|
|
1507
|
-
}
|
|
1508
|
-
this._ownedContext = null;
|
|
1509
|
-
const clone = new Graphics(this._context);
|
|
1510
|
-
return clone;
|
|
1511
|
-
}
|
|
1512
|
-
// -------- v7 deprecations ---------
|
|
1513
|
-
/**
|
|
1514
|
-
* @param width
|
|
1515
|
-
* @param color
|
|
1516
|
-
* @param alpha
|
|
1517
|
-
* @deprecated since 8.0.0 Use {@link Graphics#setStrokeStyle} instead
|
|
1518
|
-
*/
|
|
1519
|
-
lineStyle(width, color, alpha) {
|
|
1520
|
-
deprecation(v8_0_0, "Graphics#lineStyle is no longer needed. Use Graphics#setStrokeStyle to set the stroke style.");
|
|
1521
|
-
const strokeStyle = {};
|
|
1522
|
-
width && (strokeStyle.width = width);
|
|
1523
|
-
color && (strokeStyle.color = color);
|
|
1524
|
-
alpha && (strokeStyle.alpha = alpha);
|
|
1525
|
-
this.context.strokeStyle = strokeStyle;
|
|
1526
|
-
return this;
|
|
1527
|
-
}
|
|
1528
|
-
/**
|
|
1529
|
-
* @param color
|
|
1530
|
-
* @param alpha
|
|
1531
|
-
* @deprecated since 8.0.0 Use {@link Graphics#fill} instead
|
|
1532
|
-
*/
|
|
1533
|
-
beginFill(color, alpha) {
|
|
1534
|
-
deprecation(v8_0_0, "Graphics#beginFill is no longer needed. Use Graphics#fill to fill the shape with the desired style.");
|
|
1535
|
-
const fillStyle = {};
|
|
1536
|
-
if (color !== void 0)
|
|
1537
|
-
fillStyle.color = color;
|
|
1538
|
-
if (alpha !== void 0)
|
|
1539
|
-
fillStyle.alpha = alpha;
|
|
1540
|
-
this.context.fillStyle = fillStyle;
|
|
1541
|
-
return this;
|
|
1542
|
-
}
|
|
1543
|
-
/**
|
|
1544
|
-
* @deprecated since 8.0.0 Use {@link Graphics#fill} instead
|
|
1545
|
-
*/
|
|
1546
|
-
endFill() {
|
|
1547
|
-
deprecation(v8_0_0, "Graphics#endFill is no longer needed. Use Graphics#fill to fill the shape with the desired style.");
|
|
1548
|
-
this.context.fill();
|
|
1549
|
-
const strokeStyle = this.context.strokeStyle;
|
|
1550
|
-
if (strokeStyle.width !== GraphicsContext.defaultStrokeStyle.width || strokeStyle.color !== GraphicsContext.defaultStrokeStyle.color || strokeStyle.alpha !== GraphicsContext.defaultStrokeStyle.alpha) {
|
|
1551
|
-
this.context.stroke();
|
|
1552
|
-
}
|
|
1553
|
-
return this;
|
|
1554
|
-
}
|
|
1555
|
-
/**
|
|
1556
|
-
* @param {...any} args
|
|
1557
|
-
* @deprecated since 8.0.0 Use {@link Graphics#circle} instead
|
|
1558
|
-
*/
|
|
1559
|
-
drawCircle(...args) {
|
|
1560
|
-
deprecation(v8_0_0, "Graphics#drawCircle has been renamed to Graphics#circle");
|
|
1561
|
-
return this._callContextMethod("circle", args);
|
|
1562
|
-
}
|
|
1563
|
-
/**
|
|
1564
|
-
* @param {...any} args
|
|
1565
|
-
* @deprecated since 8.0.0 Use {@link Graphics#ellipse} instead
|
|
1566
|
-
*/
|
|
1567
|
-
drawEllipse(...args) {
|
|
1568
|
-
deprecation(v8_0_0, "Graphics#drawEllipse has been renamed to Graphics#ellipse");
|
|
1569
|
-
return this._callContextMethod("ellipse", args);
|
|
1570
|
-
}
|
|
1571
|
-
/**
|
|
1572
|
-
* @param {...any} args
|
|
1573
|
-
* @deprecated since 8.0.0 Use {@link Graphics#poly} instead
|
|
1574
|
-
*/
|
|
1575
|
-
drawPolygon(...args) {
|
|
1576
|
-
deprecation(v8_0_0, "Graphics#drawPolygon has been renamed to Graphics#poly");
|
|
1577
|
-
return this._callContextMethod("poly", args);
|
|
1578
|
-
}
|
|
1579
|
-
/**
|
|
1580
|
-
* @param {...any} args
|
|
1581
|
-
* @deprecated since 8.0.0 Use {@link Graphics#rect} instead
|
|
1582
|
-
*/
|
|
1583
|
-
drawRect(...args) {
|
|
1584
|
-
deprecation(v8_0_0, "Graphics#drawRect has been renamed to Graphics#rect");
|
|
1585
|
-
return this._callContextMethod("rect", args);
|
|
1586
|
-
}
|
|
1587
|
-
/**
|
|
1588
|
-
* @param {...any} args
|
|
1589
|
-
* @deprecated since 8.0.0 Use {@link Graphics#roundRect} instead
|
|
1590
|
-
*/
|
|
1591
|
-
drawRoundedRect(...args) {
|
|
1592
|
-
deprecation(v8_0_0, "Graphics#drawRoundedRect has been renamed to Graphics#roundRect");
|
|
1593
|
-
return this._callContextMethod("roundRect", args);
|
|
1594
|
-
}
|
|
1595
|
-
/**
|
|
1596
|
-
* @param {...any} args
|
|
1597
|
-
* @deprecated since 8.0.0 Use {@link Graphics#star} instead
|
|
1598
|
-
*/
|
|
1599
|
-
drawStar(...args) {
|
|
1600
|
-
deprecation(v8_0_0, "Graphics#drawStar has been renamed to Graphics#star");
|
|
1601
|
-
return this._callContextMethod("star", args);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1605
|
-
const _MeshGeometry = class _MeshGeometry extends Geometry {
|
|
1606
|
-
constructor(...args) {
|
|
1607
|
-
let options = args[0] ?? {};
|
|
1608
|
-
if (options instanceof Float32Array) {
|
|
1609
|
-
deprecation(v8_0_0, "use new MeshGeometry({ positions, uvs, indices }) instead");
|
|
1610
|
-
options = {
|
|
1611
|
-
positions: options,
|
|
1612
|
-
uvs: args[1],
|
|
1613
|
-
indices: args[2]
|
|
1614
|
-
};
|
|
1615
|
-
}
|
|
1616
|
-
options = { ..._MeshGeometry.defaultOptions, ...options };
|
|
1617
|
-
const positions = options.positions || new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
1618
|
-
let uvs = options.uvs;
|
|
1619
|
-
if (!uvs) {
|
|
1620
|
-
if (options.positions) {
|
|
1621
|
-
uvs = new Float32Array(positions.length);
|
|
1622
|
-
} else {
|
|
1623
|
-
uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
const indices = options.indices || new Uint32Array([0, 1, 2, 0, 2, 3]);
|
|
1627
|
-
const shrinkToFit = options.shrinkBuffersToFit;
|
|
1628
|
-
const positionBuffer = new Buffer({
|
|
1629
|
-
data: positions,
|
|
1630
|
-
label: "attribute-mesh-positions",
|
|
1631
|
-
shrinkToFit,
|
|
1632
|
-
usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
|
|
1633
|
-
});
|
|
1634
|
-
const uvBuffer = new Buffer({
|
|
1635
|
-
data: uvs,
|
|
1636
|
-
label: "attribute-mesh-uvs",
|
|
1637
|
-
shrinkToFit,
|
|
1638
|
-
usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
|
|
1639
|
-
});
|
|
1640
|
-
const indexBuffer = new Buffer({
|
|
1641
|
-
data: indices,
|
|
1642
|
-
label: "index-mesh-buffer",
|
|
1643
|
-
shrinkToFit,
|
|
1644
|
-
usage: BufferUsage.INDEX | BufferUsage.COPY_DST
|
|
1645
|
-
});
|
|
1646
|
-
super({
|
|
1647
|
-
attributes: {
|
|
1648
|
-
aPosition: {
|
|
1649
|
-
buffer: positionBuffer,
|
|
1650
|
-
format: "float32x2",
|
|
1651
|
-
stride: 2 * 4,
|
|
1652
|
-
offset: 0
|
|
1653
|
-
},
|
|
1654
|
-
aUV: {
|
|
1655
|
-
buffer: uvBuffer,
|
|
1656
|
-
format: "float32x2",
|
|
1657
|
-
stride: 2 * 4,
|
|
1658
|
-
offset: 0
|
|
1659
|
-
}
|
|
1660
|
-
},
|
|
1661
|
-
indexBuffer,
|
|
1662
|
-
topology: options.topology
|
|
1663
|
-
});
|
|
1664
|
-
this.batchMode = "auto";
|
|
1665
|
-
}
|
|
1666
|
-
/** The positions of the mesh. */
|
|
1667
|
-
get positions() {
|
|
1668
|
-
return this.attributes.aPosition.buffer.data;
|
|
1669
|
-
}
|
|
1670
|
-
/**
|
|
1671
|
-
* Set the positions of the mesh.
|
|
1672
|
-
* When setting the positions, its important that the uvs array is at least as long as the positions array.
|
|
1673
|
-
* otherwise the geometry will not be valid.
|
|
1674
|
-
* @param {Float32Array} value - The positions of the mesh.
|
|
1675
|
-
*/
|
|
1676
|
-
set positions(value) {
|
|
1677
|
-
this.attributes.aPosition.buffer.data = value;
|
|
1678
|
-
}
|
|
1679
|
-
/** The UVs of the mesh. */
|
|
1680
|
-
get uvs() {
|
|
1681
|
-
return this.attributes.aUV.buffer.data;
|
|
1682
|
-
}
|
|
1683
|
-
/**
|
|
1684
|
-
* Set the UVs of the mesh.
|
|
1685
|
-
* Its important that the uvs array you set is at least as long as the positions array.
|
|
1686
|
-
* otherwise the geometry will not be valid.
|
|
1687
|
-
* @param {Float32Array} value - The UVs of the mesh.
|
|
1688
|
-
*/
|
|
1689
|
-
set uvs(value) {
|
|
1690
|
-
this.attributes.aUV.buffer.data = value;
|
|
1691
|
-
}
|
|
1692
|
-
/** The indices of the mesh. */
|
|
1693
|
-
get indices() {
|
|
1694
|
-
return this.indexBuffer.data;
|
|
1695
|
-
}
|
|
1696
|
-
set indices(value) {
|
|
1697
|
-
this.indexBuffer.data = value;
|
|
1698
|
-
}
|
|
1699
|
-
};
|
|
1700
|
-
_MeshGeometry.defaultOptions = {
|
|
1701
|
-
topology: "triangle-list",
|
|
1702
|
-
shrinkBuffersToFit: false
|
|
1703
|
-
};
|
|
1704
|
-
let MeshGeometry = _MeshGeometry;
|
|
1705
|
-
|
|
1706
|
-
let _internalCanvas = null;
|
|
1707
|
-
let _internalContext = null;
|
|
1708
|
-
function ensureInternalCanvas(width, height) {
|
|
1709
|
-
if (!_internalCanvas) {
|
|
1710
|
-
_internalCanvas = DOMAdapter.get().createCanvas(256, 128);
|
|
1711
|
-
_internalContext = _internalCanvas.getContext("2d", { willReadFrequently: true });
|
|
1712
|
-
_internalContext.globalCompositeOperation = "copy";
|
|
1713
|
-
_internalContext.globalAlpha = 1;
|
|
1714
|
-
}
|
|
1715
|
-
if (_internalCanvas.width < width || _internalCanvas.height < height) {
|
|
1716
|
-
_internalCanvas.width = nextPow2(width);
|
|
1717
|
-
_internalCanvas.height = nextPow2(height);
|
|
1718
|
-
}
|
|
1719
|
-
}
|
|
1720
|
-
function checkRow(data, width, y) {
|
|
1721
|
-
for (let x = 0, index = 4 * y * width; x < width; ++x, index += 4) {
|
|
1722
|
-
if (data[index + 3] !== 0)
|
|
1723
|
-
return false;
|
|
1724
|
-
}
|
|
1725
|
-
return true;
|
|
1726
|
-
}
|
|
1727
|
-
function checkColumn(data, width, x, top, bottom) {
|
|
1728
|
-
const stride = 4 * width;
|
|
1729
|
-
for (let y = top, index = top * stride + 4 * x; y <= bottom; ++y, index += stride) {
|
|
1730
|
-
if (data[index + 3] !== 0)
|
|
1731
|
-
return false;
|
|
1732
|
-
}
|
|
1733
|
-
return true;
|
|
1734
|
-
}
|
|
1735
|
-
function getCanvasBoundingBox(...args) {
|
|
1736
|
-
let options = args[0];
|
|
1737
|
-
if (!options.canvas) {
|
|
1738
|
-
options = { canvas: args[0], resolution: args[1] };
|
|
1739
|
-
}
|
|
1740
|
-
const { canvas } = options;
|
|
1741
|
-
const resolution = Math.min(options.resolution ?? 1, 1);
|
|
1742
|
-
const width = options.width ?? canvas.width;
|
|
1743
|
-
const height = options.height ?? canvas.height;
|
|
1744
|
-
let output = options.output;
|
|
1745
|
-
ensureInternalCanvas(width, height);
|
|
1746
|
-
if (!_internalContext) {
|
|
1747
|
-
throw new TypeError("Failed to get canvas 2D context");
|
|
1748
|
-
}
|
|
1749
|
-
_internalContext.drawImage(
|
|
1750
|
-
canvas,
|
|
1751
|
-
0,
|
|
1752
|
-
0,
|
|
1753
|
-
width,
|
|
1754
|
-
height,
|
|
1755
|
-
0,
|
|
1756
|
-
0,
|
|
1757
|
-
width * resolution,
|
|
1758
|
-
height * resolution
|
|
1759
|
-
);
|
|
1760
|
-
const imageData = _internalContext.getImageData(0, 0, width, height);
|
|
1761
|
-
const data = imageData.data;
|
|
1762
|
-
let left = 0;
|
|
1763
|
-
let top = 0;
|
|
1764
|
-
let right = width - 1;
|
|
1765
|
-
let bottom = height - 1;
|
|
1766
|
-
while (top < height && checkRow(data, width, top))
|
|
1767
|
-
++top;
|
|
1768
|
-
if (top === height)
|
|
1769
|
-
return Rectangle.EMPTY;
|
|
1770
|
-
while (checkRow(data, width, bottom))
|
|
1771
|
-
--bottom;
|
|
1772
|
-
while (checkColumn(data, width, left, top, bottom))
|
|
1773
|
-
++left;
|
|
1774
|
-
while (checkColumn(data, width, right, top, bottom))
|
|
1775
|
-
--right;
|
|
1776
|
-
++right;
|
|
1777
|
-
++bottom;
|
|
1778
|
-
_internalContext.globalCompositeOperation = "source-over";
|
|
1779
|
-
_internalContext.strokeRect(left, top, right - left, bottom - top);
|
|
1780
|
-
_internalContext.globalCompositeOperation = "copy";
|
|
1781
|
-
output ?? (output = new Rectangle());
|
|
1782
|
-
output.set(left / resolution, top / resolution, (right - left) / resolution, (bottom - top) / resolution);
|
|
1783
|
-
return output;
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
const tempRect = new Rectangle();
|
|
1787
|
-
class CanvasTextGeneratorClass {
|
|
1788
|
-
/**
|
|
1789
|
-
* Creates a canvas with the specified text rendered to it.
|
|
1790
|
-
*
|
|
1791
|
-
* Generates a canvas of appropriate size, renders the text with the provided style,
|
|
1792
|
-
* and returns both the canvas/context and a Rectangle representing the text bounds.
|
|
1793
|
-
*
|
|
1794
|
-
* When trim is enabled in the style, the frame will represent the bounds of the
|
|
1795
|
-
* non-transparent pixels, which can be smaller than the full canvas.
|
|
1796
|
-
* @param options - The options for generating the text canvas
|
|
1797
|
-
* @param options.text - The text to render
|
|
1798
|
-
* @param options.style - The style to apply to the text
|
|
1799
|
-
* @param options.resolution - The resolution of the canvas (defaults to 1)
|
|
1800
|
-
* @param options.padding
|
|
1801
|
-
* @returns An object containing the canvas/context and the frame (bounds) of the text
|
|
1802
|
-
*/
|
|
1803
|
-
getCanvasAndContext(options) {
|
|
1804
|
-
const { text, style, resolution = 1 } = options;
|
|
1805
|
-
const padding = style._getFinalPadding();
|
|
1806
|
-
const measured = CanvasTextMetrics.measureText(text || " ", style);
|
|
1807
|
-
const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + padding * 2) * resolution);
|
|
1808
|
-
const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + padding * 2) * resolution);
|
|
1809
|
-
const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(width, height);
|
|
1810
|
-
this._renderTextToCanvas(text, style, padding, resolution, canvasAndContext);
|
|
1811
|
-
const frame = style.trim ? getCanvasBoundingBox({ canvas: canvasAndContext.canvas, width, height, resolution: 1, output: tempRect }) : tempRect.set(0, 0, width, height);
|
|
1812
|
-
return {
|
|
1813
|
-
canvasAndContext,
|
|
1814
|
-
frame
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
/**
|
|
1818
|
-
* Returns a canvas and context to the pool.
|
|
1819
|
-
*
|
|
1820
|
-
* This should be called when you're done with the canvas to allow reuse
|
|
1821
|
-
* and prevent memory leaks.
|
|
1822
|
-
* @param canvasAndContext - The canvas and context to return to the pool
|
|
1823
|
-
*/
|
|
1824
|
-
returnCanvasAndContext(canvasAndContext) {
|
|
1825
|
-
CanvasPool.returnCanvasAndContext(canvasAndContext);
|
|
1826
|
-
}
|
|
1827
|
-
/**
|
|
1828
|
-
* Renders text to its canvas, and updates its texture.
|
|
1829
|
-
* @param text - The text to render
|
|
1830
|
-
* @param style - The style of the text
|
|
1831
|
-
* @param padding - The padding of the text
|
|
1832
|
-
* @param resolution - The resolution of the text
|
|
1833
|
-
* @param canvasAndContext - The canvas and context to render the text to
|
|
1834
|
-
*/
|
|
1835
|
-
_renderTextToCanvas(text, style, padding, resolution, canvasAndContext) {
|
|
1836
|
-
const { canvas, context } = canvasAndContext;
|
|
1837
|
-
const font = fontStringFromTextStyle(style);
|
|
1838
|
-
const measured = CanvasTextMetrics.measureText(text || " ", style);
|
|
1839
|
-
const lines = measured.lines;
|
|
1840
|
-
const lineHeight = measured.lineHeight;
|
|
1841
|
-
const lineWidths = measured.lineWidths;
|
|
1842
|
-
const maxLineWidth = measured.maxLineWidth;
|
|
1843
|
-
const fontProperties = measured.fontProperties;
|
|
1844
|
-
const height = canvas.height;
|
|
1845
|
-
context.resetTransform();
|
|
1846
|
-
context.scale(resolution, resolution);
|
|
1847
|
-
context.textBaseline = style.textBaseline;
|
|
1848
|
-
if (style._stroke?.width) {
|
|
1849
|
-
const strokeStyle = style._stroke;
|
|
1850
|
-
context.lineWidth = strokeStyle.width;
|
|
1851
|
-
context.miterLimit = strokeStyle.miterLimit;
|
|
1852
|
-
context.lineJoin = strokeStyle.join;
|
|
1853
|
-
context.lineCap = strokeStyle.cap;
|
|
1854
|
-
}
|
|
1855
|
-
context.font = font;
|
|
1856
|
-
let linePositionX;
|
|
1857
|
-
let linePositionY;
|
|
1858
|
-
const passesCount = style.dropShadow ? 2 : 1;
|
|
1859
|
-
for (let i = 0; i < passesCount; ++i) {
|
|
1860
|
-
const isShadowPass = style.dropShadow && i === 0;
|
|
1861
|
-
const dsOffsetText = isShadowPass ? Math.ceil(Math.max(1, height) + padding * 2) : 0;
|
|
1862
|
-
const dsOffsetShadow = dsOffsetText * resolution;
|
|
1863
|
-
if (isShadowPass) {
|
|
1864
|
-
context.fillStyle = "black";
|
|
1865
|
-
context.strokeStyle = "black";
|
|
1866
|
-
const shadowOptions = style.dropShadow;
|
|
1867
|
-
const dropShadowColor = shadowOptions.color;
|
|
1868
|
-
const dropShadowAlpha = shadowOptions.alpha;
|
|
1869
|
-
context.shadowColor = Color.shared.setValue(dropShadowColor).setAlpha(dropShadowAlpha).toRgbaString();
|
|
1870
|
-
const dropShadowBlur = shadowOptions.blur * resolution;
|
|
1871
|
-
const dropShadowDistance = shadowOptions.distance * resolution;
|
|
1872
|
-
context.shadowBlur = dropShadowBlur;
|
|
1873
|
-
context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance;
|
|
1874
|
-
context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance + dsOffsetShadow;
|
|
1875
|
-
} else {
|
|
1876
|
-
context.fillStyle = style._fill ? getCanvasFillStyle(style._fill, context, measured) : null;
|
|
1877
|
-
if (style._stroke?.width) {
|
|
1878
|
-
const padding2 = style._stroke.width * style._stroke.alignment;
|
|
1879
|
-
context.strokeStyle = getCanvasFillStyle(style._stroke, context, measured, padding2);
|
|
1880
|
-
}
|
|
1881
|
-
context.shadowColor = "black";
|
|
1882
|
-
}
|
|
1883
|
-
let linePositionYShift = (lineHeight - fontProperties.fontSize) / 2;
|
|
1884
|
-
if (lineHeight - fontProperties.fontSize < 0) {
|
|
1885
|
-
linePositionYShift = 0;
|
|
1886
|
-
}
|
|
1887
|
-
const strokeWidth = style._stroke?.width ?? 0;
|
|
1888
|
-
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
1889
|
-
linePositionX = strokeWidth / 2;
|
|
1890
|
-
linePositionY = strokeWidth / 2 + i2 * lineHeight + fontProperties.ascent + linePositionYShift;
|
|
1891
|
-
if (style.align === "right") {
|
|
1892
|
-
linePositionX += maxLineWidth - lineWidths[i2];
|
|
1893
|
-
} else if (style.align === "center") {
|
|
1894
|
-
linePositionX += (maxLineWidth - lineWidths[i2]) / 2;
|
|
1895
|
-
}
|
|
1896
|
-
if (style._stroke?.width) {
|
|
1897
|
-
this._drawLetterSpacing(
|
|
1898
|
-
lines[i2],
|
|
1899
|
-
style,
|
|
1900
|
-
canvasAndContext,
|
|
1901
|
-
linePositionX + padding,
|
|
1902
|
-
linePositionY + padding - dsOffsetText,
|
|
1903
|
-
true
|
|
1904
|
-
);
|
|
1905
|
-
}
|
|
1906
|
-
if (style._fill !== void 0) {
|
|
1907
|
-
this._drawLetterSpacing(
|
|
1908
|
-
lines[i2],
|
|
1909
|
-
style,
|
|
1910
|
-
canvasAndContext,
|
|
1911
|
-
linePositionX + padding,
|
|
1912
|
-
linePositionY + padding - dsOffsetText
|
|
1913
|
-
);
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
/**
|
|
1919
|
-
* Render the text with letter-spacing.
|
|
1920
|
-
*
|
|
1921
|
-
* This method handles rendering text with the correct letter spacing, using either:
|
|
1922
|
-
* 1. Native letter spacing if supported by the browser
|
|
1923
|
-
* 2. Manual letter spacing calculation if not natively supported
|
|
1924
|
-
*
|
|
1925
|
-
* For manual letter spacing, it calculates the position of each character
|
|
1926
|
-
* based on its width and the desired spacing.
|
|
1927
|
-
* @param text - The text to draw
|
|
1928
|
-
* @param style - The text style to apply
|
|
1929
|
-
* @param canvasAndContext - The canvas and context to draw to
|
|
1930
|
-
* @param x - Horizontal position to draw the text
|
|
1931
|
-
* @param y - Vertical position to draw the text
|
|
1932
|
-
* @param isStroke - Whether to render the stroke (true) or fill (false)
|
|
1933
|
-
* @private
|
|
1934
|
-
*/
|
|
1935
|
-
_drawLetterSpacing(text, style, canvasAndContext, x, y, isStroke = false) {
|
|
1936
|
-
const { context } = canvasAndContext;
|
|
1937
|
-
const letterSpacing = style.letterSpacing;
|
|
1938
|
-
let useExperimentalLetterSpacing = false;
|
|
1939
|
-
if (CanvasTextMetrics.experimentalLetterSpacingSupported) {
|
|
1940
|
-
if (CanvasTextMetrics.experimentalLetterSpacing) {
|
|
1941
|
-
context.letterSpacing = `${letterSpacing}px`;
|
|
1942
|
-
context.textLetterSpacing = `${letterSpacing}px`;
|
|
1943
|
-
useExperimentalLetterSpacing = true;
|
|
1944
|
-
} else {
|
|
1945
|
-
context.letterSpacing = "0px";
|
|
1946
|
-
context.textLetterSpacing = "0px";
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
if (letterSpacing === 0 || useExperimentalLetterSpacing) {
|
|
1950
|
-
if (isStroke) {
|
|
1951
|
-
context.strokeText(text, x, y);
|
|
1952
|
-
} else {
|
|
1953
|
-
context.fillText(text, x, y);
|
|
1954
|
-
}
|
|
1955
|
-
return;
|
|
1956
|
-
}
|
|
1957
|
-
let currentPosition = x;
|
|
1958
|
-
const stringArray = CanvasTextMetrics.graphemeSegmenter(text);
|
|
1959
|
-
let previousWidth = context.measureText(text).width;
|
|
1960
|
-
let currentWidth = 0;
|
|
1961
|
-
for (let i = 0; i < stringArray.length; ++i) {
|
|
1962
|
-
const currentChar = stringArray[i];
|
|
1963
|
-
if (isStroke) {
|
|
1964
|
-
context.strokeText(currentChar, currentPosition, y);
|
|
1965
|
-
} else {
|
|
1966
|
-
context.fillText(currentChar, currentPosition, y);
|
|
1967
|
-
}
|
|
1968
|
-
let textStr = "";
|
|
1969
|
-
for (let j = i + 1; j < stringArray.length; ++j) {
|
|
1970
|
-
textStr += stringArray[j];
|
|
1971
|
-
}
|
|
1972
|
-
currentWidth = context.measureText(textStr).width;
|
|
1973
|
-
currentPosition += previousWidth - currentWidth + letterSpacing;
|
|
1974
|
-
previousWidth = currentWidth;
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
const CanvasTextGenerator = new CanvasTextGeneratorClass();
|
|
1979
|
-
|
|
1980
|
-
function textStyleToCSS(style) {
|
|
1981
|
-
const stroke = style._stroke;
|
|
1982
|
-
const fill = style._fill;
|
|
1983
|
-
const cssStyleString = [
|
|
1984
|
-
`color: ${Color.shared.setValue(fill.color).toHex()}`,
|
|
1985
|
-
`font-size: ${style.fontSize}px`,
|
|
1986
|
-
`font-family: ${style.fontFamily}`,
|
|
1987
|
-
`font-weight: ${style.fontWeight}`,
|
|
1988
|
-
`font-style: ${style.fontStyle}`,
|
|
1989
|
-
`font-variant: ${style.fontVariant}`,
|
|
1990
|
-
`letter-spacing: ${style.letterSpacing}px`,
|
|
1991
|
-
`text-align: ${style.align}`,
|
|
1992
|
-
`padding: ${style.padding}px`,
|
|
1993
|
-
`white-space: ${style.whiteSpace === "pre" && style.wordWrap ? "pre-wrap" : style.whiteSpace}`,
|
|
1994
|
-
...style.lineHeight ? [`line-height: ${style.lineHeight}px`] : [],
|
|
1995
|
-
...style.wordWrap ? [
|
|
1996
|
-
`word-wrap: ${style.breakWords ? "break-all" : "break-word"}`,
|
|
1997
|
-
`max-width: ${style.wordWrapWidth}px`
|
|
1998
|
-
] : [],
|
|
1999
|
-
...stroke ? [strokeToCSS(stroke)] : [],
|
|
2000
|
-
...style.dropShadow ? [dropShadowToCSS(style.dropShadow)] : [],
|
|
2001
|
-
...style.cssOverrides
|
|
2002
|
-
].join(";");
|
|
2003
|
-
const cssStyles = [`div { ${cssStyleString} }`];
|
|
2004
|
-
tagStyleToCSS(style.tagStyles, cssStyles);
|
|
2005
|
-
return cssStyles.join(" ");
|
|
2006
|
-
}
|
|
2007
|
-
function dropShadowToCSS(dropShadowStyle) {
|
|
2008
|
-
const color = Color.shared.setValue(dropShadowStyle.color).setAlpha(dropShadowStyle.alpha).toHexa();
|
|
2009
|
-
const x = Math.round(Math.cos(dropShadowStyle.angle) * dropShadowStyle.distance);
|
|
2010
|
-
const y = Math.round(Math.sin(dropShadowStyle.angle) * dropShadowStyle.distance);
|
|
2011
|
-
const position = `${x}px ${y}px`;
|
|
2012
|
-
if (dropShadowStyle.blur > 0) {
|
|
2013
|
-
return `text-shadow: ${position} ${dropShadowStyle.blur}px ${color}`;
|
|
2014
|
-
}
|
|
2015
|
-
return `text-shadow: ${position} ${color}`;
|
|
2016
|
-
}
|
|
2017
|
-
function strokeToCSS(stroke) {
|
|
2018
|
-
return [
|
|
2019
|
-
`-webkit-text-stroke-width: ${stroke.width}px`,
|
|
2020
|
-
`-webkit-text-stroke-color: ${Color.shared.setValue(stroke.color).toHex()}`,
|
|
2021
|
-
`text-stroke-width: ${stroke.width}px`,
|
|
2022
|
-
`text-stroke-color: ${Color.shared.setValue(stroke.color).toHex()}`,
|
|
2023
|
-
"paint-order: stroke"
|
|
2024
|
-
].join(";");
|
|
2025
|
-
}
|
|
2026
|
-
const templates = {
|
|
2027
|
-
fontSize: `font-size: {{VALUE}}px`,
|
|
2028
|
-
fontFamily: `font-family: {{VALUE}}`,
|
|
2029
|
-
fontWeight: `font-weight: {{VALUE}}`,
|
|
2030
|
-
fontStyle: `font-style: {{VALUE}}`,
|
|
2031
|
-
fontVariant: `font-variant: {{VALUE}}`,
|
|
2032
|
-
letterSpacing: `letter-spacing: {{VALUE}}px`,
|
|
2033
|
-
align: `text-align: {{VALUE}}`,
|
|
2034
|
-
padding: `padding: {{VALUE}}px`,
|
|
2035
|
-
whiteSpace: `white-space: {{VALUE}}`,
|
|
2036
|
-
lineHeight: `line-height: {{VALUE}}px`,
|
|
2037
|
-
wordWrapWidth: `max-width: {{VALUE}}px`
|
|
2038
|
-
};
|
|
2039
|
-
const transform = {
|
|
2040
|
-
fill: (value) => `color: ${Color.shared.setValue(value).toHex()}`,
|
|
2041
|
-
breakWords: (value) => `word-wrap: ${value ? "break-all" : "break-word"}`,
|
|
2042
|
-
stroke: strokeToCSS,
|
|
2043
|
-
dropShadow: dropShadowToCSS
|
|
2044
|
-
};
|
|
2045
|
-
function tagStyleToCSS(tagStyles, out) {
|
|
2046
|
-
for (const i in tagStyles) {
|
|
2047
|
-
const tagStyle = tagStyles[i];
|
|
2048
|
-
const cssTagStyle = [];
|
|
2049
|
-
for (const j in tagStyle) {
|
|
2050
|
-
if (transform[j]) {
|
|
2051
|
-
cssTagStyle.push(transform[j](tagStyle[j]));
|
|
2052
|
-
} else if (templates[j]) {
|
|
2053
|
-
cssTagStyle.push(templates[j].replace("{{VALUE}}", tagStyle[j]));
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
out.push(`${i} { ${cssTagStyle.join(";")} }`);
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
class HTMLTextStyle extends TextStyle {
|
|
2061
|
-
constructor(options = {}) {
|
|
2062
|
-
super(options);
|
|
2063
|
-
this._cssOverrides = [];
|
|
2064
|
-
this.cssOverrides = options.cssOverrides ?? [];
|
|
2065
|
-
this.tagStyles = options.tagStyles ?? {};
|
|
2066
|
-
}
|
|
2067
|
-
/**
|
|
2068
|
-
* List of CSS style overrides to apply to the HTML text.
|
|
2069
|
-
* These styles are added after the built-in styles and can override any default styling.
|
|
2070
|
-
* @advanced
|
|
2071
|
-
*/
|
|
2072
|
-
set cssOverrides(value) {
|
|
2073
|
-
this._cssOverrides = value instanceof Array ? value : [value];
|
|
2074
|
-
this.update();
|
|
2075
|
-
}
|
|
2076
|
-
/** @advanced */
|
|
2077
|
-
get cssOverrides() {
|
|
2078
|
-
return this._cssOverrides;
|
|
2079
|
-
}
|
|
2080
|
-
/**
|
|
2081
|
-
* Updates the text style and triggers a refresh of the CSS style cache.
|
|
2082
|
-
* This method is called automatically when style properties are changed.
|
|
2083
|
-
* @example
|
|
2084
|
-
* ```ts
|
|
2085
|
-
* // Update after multiple changes
|
|
2086
|
-
* const text = new HTMLText({
|
|
2087
|
-
* text: 'Hello World',
|
|
2088
|
-
* style
|
|
2089
|
-
* });
|
|
2090
|
-
*
|
|
2091
|
-
* style.fontSize = 32;
|
|
2092
|
-
* style.fill = '#00ff00';
|
|
2093
|
-
* style.fontFamily = 'Arial';
|
|
2094
|
-
* style.update(); // Apply all changes at once
|
|
2095
|
-
* ```
|
|
2096
|
-
* @advanced
|
|
2097
|
-
* @see {@link HTMLTextStyle#cssStyle} For accessing the generated CSS
|
|
2098
|
-
* @see {@link HTMLTextStyle#cssOverrides} For managing CSS overrides
|
|
2099
|
-
*/
|
|
2100
|
-
update() {
|
|
2101
|
-
this._cssStyle = null;
|
|
2102
|
-
super.update();
|
|
2103
|
-
}
|
|
2104
|
-
/**
|
|
2105
|
-
* Creates a new HTMLTextStyle object with the same values as this one.
|
|
2106
|
-
* This creates a deep copy of all style properties, including dropShadow and tag styles.
|
|
2107
|
-
* @example
|
|
2108
|
-
* ```ts
|
|
2109
|
-
* // Create original style
|
|
2110
|
-
* const originalStyle = new HTMLTextStyle({
|
|
2111
|
-
* fontSize: 24,
|
|
2112
|
-
* fill: '#ff0000',
|
|
2113
|
-
* tagStyles: {
|
|
2114
|
-
* header: { fontSize: 32, fill: '#00ff00' }
|
|
2115
|
-
* }
|
|
2116
|
-
* });
|
|
2117
|
-
*
|
|
2118
|
-
* // Clone the style
|
|
2119
|
-
* const clonedStyle = originalStyle.clone();
|
|
2120
|
-
*
|
|
2121
|
-
* // Modify cloned style independently
|
|
2122
|
-
* clonedStyle.fontSize = 36;
|
|
2123
|
-
* clonedStyle.fill = '#0000ff';
|
|
2124
|
-
*
|
|
2125
|
-
* // Original style remains unchanged
|
|
2126
|
-
* console.log(originalStyle.fontSize); // Still 24
|
|
2127
|
-
* console.log(originalStyle.fill); // Still '#ff0000'
|
|
2128
|
-
* ```
|
|
2129
|
-
*
|
|
2130
|
-
* Properties that are cloned:
|
|
2131
|
-
* - Basic text properties (fontSize, fontFamily, etc.)
|
|
2132
|
-
* - Fill and stroke styles
|
|
2133
|
-
* - Drop shadow configuration
|
|
2134
|
-
* - CSS overrides
|
|
2135
|
-
* - Tag styles (deep copied)
|
|
2136
|
-
* - Word wrap settings
|
|
2137
|
-
* - Alignment and spacing
|
|
2138
|
-
* @returns {HTMLTextStyle} A new HTMLTextStyle instance with the same properties
|
|
2139
|
-
* @see {@link HTMLTextStyle} For available style properties
|
|
2140
|
-
* @see {@link HTMLTextStyle#cssOverrides} For CSS override handling
|
|
2141
|
-
* @see {@link HTMLTextStyle#tagStyles} For tag style configuration
|
|
2142
|
-
* @standard
|
|
2143
|
-
*/
|
|
2144
|
-
clone() {
|
|
2145
|
-
return new HTMLTextStyle({
|
|
2146
|
-
align: this.align,
|
|
2147
|
-
breakWords: this.breakWords,
|
|
2148
|
-
dropShadow: this.dropShadow ? { ...this.dropShadow } : null,
|
|
2149
|
-
fill: this._fill,
|
|
2150
|
-
fontFamily: this.fontFamily,
|
|
2151
|
-
fontSize: this.fontSize,
|
|
2152
|
-
fontStyle: this.fontStyle,
|
|
2153
|
-
fontVariant: this.fontVariant,
|
|
2154
|
-
fontWeight: this.fontWeight,
|
|
2155
|
-
letterSpacing: this.letterSpacing,
|
|
2156
|
-
lineHeight: this.lineHeight,
|
|
2157
|
-
padding: this.padding,
|
|
2158
|
-
stroke: this._stroke,
|
|
2159
|
-
whiteSpace: this.whiteSpace,
|
|
2160
|
-
wordWrap: this.wordWrap,
|
|
2161
|
-
wordWrapWidth: this.wordWrapWidth,
|
|
2162
|
-
cssOverrides: this.cssOverrides,
|
|
2163
|
-
tagStyles: { ...this.tagStyles }
|
|
2164
|
-
});
|
|
2165
|
-
}
|
|
2166
|
-
/**
|
|
2167
|
-
* The CSS style string that will be applied to the HTML text.
|
|
2168
|
-
* @advanced
|
|
2169
|
-
*/
|
|
2170
|
-
get cssStyle() {
|
|
2171
|
-
if (!this._cssStyle) {
|
|
2172
|
-
this._cssStyle = textStyleToCSS(this);
|
|
2173
|
-
}
|
|
2174
|
-
return this._cssStyle;
|
|
2175
|
-
}
|
|
2176
|
-
/**
|
|
2177
|
-
* Add a style override, this can be any CSS property
|
|
2178
|
-
* it will override any built-in style. This is the
|
|
2179
|
-
* property and the value as a string (e.g., `color: red`).
|
|
2180
|
-
* This will override any other internal style.
|
|
2181
|
-
* @param {string} value - CSS style(s) to add.
|
|
2182
|
-
* @example
|
|
2183
|
-
* style.addOverride('background-color: red');
|
|
2184
|
-
* @advanced
|
|
2185
|
-
*/
|
|
2186
|
-
addOverride(...value) {
|
|
2187
|
-
const toAdd = value.filter((v) => !this.cssOverrides.includes(v));
|
|
2188
|
-
if (toAdd.length > 0) {
|
|
2189
|
-
this.cssOverrides.push(...toAdd);
|
|
2190
|
-
this.update();
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
/**
|
|
2194
|
-
* Remove any overrides that match the value.
|
|
2195
|
-
* @param {string} value - CSS style to remove.
|
|
2196
|
-
* @example
|
|
2197
|
-
* style.removeOverride('background-color: red');
|
|
2198
|
-
* @advanced
|
|
2199
|
-
*/
|
|
2200
|
-
removeOverride(...value) {
|
|
2201
|
-
const toRemove = value.filter((v) => this.cssOverrides.includes(v));
|
|
2202
|
-
if (toRemove.length > 0) {
|
|
2203
|
-
this.cssOverrides = this.cssOverrides.filter((v) => !toRemove.includes(v));
|
|
2204
|
-
this.update();
|
|
2205
|
-
}
|
|
2206
|
-
}
|
|
2207
|
-
/**
|
|
2208
|
-
* Sets the fill style for the text. HTML text only supports color fills (string or number values).
|
|
2209
|
-
* Texture fills are not supported and will trigger a warning in debug mode.
|
|
2210
|
-
* @example
|
|
2211
|
-
* ```ts
|
|
2212
|
-
* // Using hex colors
|
|
2213
|
-
* const text = new HTMLText({
|
|
2214
|
-
* text: 'Colored Text',
|
|
2215
|
-
* style: {
|
|
2216
|
-
* fill: 0xff0000 // Red color
|
|
2217
|
-
* }
|
|
2218
|
-
* });
|
|
2219
|
-
*
|
|
2220
|
-
* // Using CSS color strings
|
|
2221
|
-
* text.style.fill = '#00ff00'; // Hex string (Green)
|
|
2222
|
-
* text.style.fill = 'blue'; // Named color
|
|
2223
|
-
* text.style.fill = 'rgb(255,0,0)' // RGB
|
|
2224
|
-
* text.style.fill = '#f0f'; // Short hex
|
|
2225
|
-
*
|
|
2226
|
-
* // Invalid usage (will trigger warning in debug)
|
|
2227
|
-
* text.style.fill = {
|
|
2228
|
-
* type: 'pattern',
|
|
2229
|
-
* texture: Texture.from('pattern.png')
|
|
2230
|
-
* }; // Not supported, falls back to default
|
|
2231
|
-
* ```
|
|
2232
|
-
* @param value - The fill color to use. Must be a string or number.
|
|
2233
|
-
* @throws {Warning} In debug mode when attempting to use unsupported fill types
|
|
2234
|
-
* @see {@link TextStyle#fill} For full fill options in canvas text
|
|
2235
|
-
* @standard
|
|
2236
|
-
*/
|
|
2237
|
-
set fill(value) {
|
|
2238
|
-
if (typeof value !== "string" && typeof value !== "number") {
|
|
2239
|
-
warn("[HTMLTextStyle] only color fill is not supported by HTMLText");
|
|
2240
|
-
}
|
|
2241
|
-
super.fill = value;
|
|
2242
|
-
}
|
|
2243
|
-
/**
|
|
2244
|
-
* Sets the stroke style for the text. HTML text only supports color strokes (string or number values).
|
|
2245
|
-
* Texture strokes are not supported and will trigger a warning in debug mode.
|
|
2246
|
-
* @example
|
|
2247
|
-
* ```ts
|
|
2248
|
-
* // Using hex colors
|
|
2249
|
-
* const text = new HTMLText({
|
|
2250
|
-
* text: 'Outlined Text',
|
|
2251
|
-
* style: {
|
|
2252
|
-
* stroke: 0xff0000 // Red outline
|
|
2253
|
-
* }
|
|
2254
|
-
* });
|
|
2255
|
-
*
|
|
2256
|
-
* // Using CSS color strings
|
|
2257
|
-
* text.style.stroke = '#00ff00'; // Hex string (Green)
|
|
2258
|
-
* text.style.stroke = 'blue'; // Named color
|
|
2259
|
-
* text.style.stroke = 'rgb(255,0,0)' // RGB
|
|
2260
|
-
* text.style.stroke = '#f0f'; // Short hex
|
|
2261
|
-
*
|
|
2262
|
-
* // Using stroke width
|
|
2263
|
-
* text.style = {
|
|
2264
|
-
* stroke: {
|
|
2265
|
-
* color: '#ff0000',
|
|
2266
|
-
* width: 2
|
|
2267
|
-
* }
|
|
2268
|
-
* };
|
|
2269
|
-
*
|
|
2270
|
-
* // Remove stroke
|
|
2271
|
-
* text.style.stroke = null;
|
|
2272
|
-
*
|
|
2273
|
-
* // Invalid usage (will trigger warning in debug)
|
|
2274
|
-
* text.style.stroke = {
|
|
2275
|
-
* type: 'pattern',
|
|
2276
|
-
* texture: Texture.from('pattern.png')
|
|
2277
|
-
* }; // Not supported, falls back to default
|
|
2278
|
-
* ```
|
|
2279
|
-
* @param value - The stroke style to use. Must be a string, number, or stroke configuration object
|
|
2280
|
-
* @throws {Warning} In debug mode when attempting to use unsupported stroke types
|
|
2281
|
-
* @see {@link TextStyle#stroke} For full stroke options in canvas text
|
|
2282
|
-
* @standard
|
|
2283
|
-
*/
|
|
2284
|
-
set stroke(value) {
|
|
2285
|
-
if (value && typeof value !== "string" && typeof value !== "number") {
|
|
2286
|
-
warn("[HTMLTextStyle] only color stroke is not supported by HTMLText");
|
|
2287
|
-
}
|
|
2288
|
-
super.stroke = value;
|
|
2289
|
-
}
|
|
2290
|
-
}
|
|
2291
|
-
|
|
2292
|
-
const nssvg = "http://www.w3.org/2000/svg";
|
|
2293
|
-
const nsxhtml = "http://www.w3.org/1999/xhtml";
|
|
2294
|
-
class HTMLTextRenderData {
|
|
2295
|
-
constructor() {
|
|
2296
|
-
this.svgRoot = document.createElementNS(nssvg, "svg");
|
|
2297
|
-
this.foreignObject = document.createElementNS(nssvg, "foreignObject");
|
|
2298
|
-
this.domElement = document.createElementNS(nsxhtml, "div");
|
|
2299
|
-
this.styleElement = document.createElementNS(nsxhtml, "style");
|
|
2300
|
-
this.image = new Image();
|
|
2301
|
-
const { foreignObject, svgRoot, styleElement, domElement } = this;
|
|
2302
|
-
foreignObject.setAttribute("width", "10000");
|
|
2303
|
-
foreignObject.setAttribute("height", "10000");
|
|
2304
|
-
foreignObject.style.overflow = "hidden";
|
|
2305
|
-
svgRoot.appendChild(foreignObject);
|
|
2306
|
-
foreignObject.appendChild(styleElement);
|
|
2307
|
-
foreignObject.appendChild(domElement);
|
|
2308
|
-
}
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
let tempHTMLTextRenderData;
|
|
2312
|
-
function measureHtmlText(text, style, fontStyleCSS, htmlTextRenderData) {
|
|
2313
|
-
htmlTextRenderData || (htmlTextRenderData = tempHTMLTextRenderData || (tempHTMLTextRenderData = new HTMLTextRenderData()));
|
|
2314
|
-
const { domElement, styleElement, svgRoot } = htmlTextRenderData;
|
|
2315
|
-
domElement.innerHTML = `<style>${style.cssStyle};</style><div style='padding:0'>${text}</div>`;
|
|
2316
|
-
domElement.setAttribute("style", "transform-origin: top left; display: inline-block");
|
|
2317
|
-
if (fontStyleCSS) {
|
|
2318
|
-
styleElement.textContent = fontStyleCSS;
|
|
2319
|
-
}
|
|
2320
|
-
document.body.appendChild(svgRoot);
|
|
2321
|
-
const contentBounds = domElement.getBoundingClientRect();
|
|
2322
|
-
svgRoot.remove();
|
|
2323
|
-
const doublePadding = style.padding * 2;
|
|
2324
|
-
return {
|
|
2325
|
-
width: contentBounds.width - doublePadding,
|
|
2326
|
-
height: contentBounds.height - doublePadding
|
|
2327
|
-
};
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
class GraphicsGpuData {
|
|
2331
|
-
constructor() {
|
|
2332
|
-
this.batches = [];
|
|
2333
|
-
this.batched = false;
|
|
2334
|
-
}
|
|
2335
|
-
destroy() {
|
|
2336
|
-
this.batches.forEach((batch) => {
|
|
2337
|
-
BigPool.return(batch);
|
|
2338
|
-
});
|
|
2339
|
-
this.batches.length = 0;
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
|
-
class GraphicsPipe {
|
|
2343
|
-
constructor(renderer, adaptor) {
|
|
2344
|
-
this.state = State.for2d();
|
|
2345
|
-
this.renderer = renderer;
|
|
2346
|
-
this._adaptor = adaptor;
|
|
2347
|
-
this.renderer.runners.contextChange.add(this);
|
|
2348
|
-
}
|
|
2349
|
-
contextChange() {
|
|
2350
|
-
this._adaptor.contextChange(this.renderer);
|
|
2351
|
-
}
|
|
2352
|
-
validateRenderable(graphics) {
|
|
2353
|
-
const context = graphics.context;
|
|
2354
|
-
const wasBatched = !!graphics._gpuData;
|
|
2355
|
-
const gpuContext = this.renderer.graphicsContext.updateGpuContext(context);
|
|
2356
|
-
if (gpuContext.isBatchable || wasBatched !== gpuContext.isBatchable) {
|
|
2357
|
-
return true;
|
|
2358
|
-
}
|
|
2359
|
-
return false;
|
|
2360
|
-
}
|
|
2361
|
-
addRenderable(graphics, instructionSet) {
|
|
2362
|
-
const gpuContext = this.renderer.graphicsContext.updateGpuContext(graphics.context);
|
|
2363
|
-
if (graphics.didViewUpdate) {
|
|
2364
|
-
this._rebuild(graphics);
|
|
2365
|
-
}
|
|
2366
|
-
if (gpuContext.isBatchable) {
|
|
2367
|
-
this._addToBatcher(graphics, instructionSet);
|
|
2368
|
-
} else {
|
|
2369
|
-
this.renderer.renderPipes.batch.break(instructionSet);
|
|
2370
|
-
instructionSet.add(graphics);
|
|
2371
|
-
}
|
|
2372
|
-
}
|
|
2373
|
-
updateRenderable(graphics) {
|
|
2374
|
-
const gpuData = this._getGpuDataForRenderable(graphics);
|
|
2375
|
-
const batches = gpuData.batches;
|
|
2376
|
-
for (let i = 0; i < batches.length; i++) {
|
|
2377
|
-
const batch = batches[i];
|
|
2378
|
-
batch._batcher.updateElement(batch);
|
|
2379
|
-
}
|
|
2380
|
-
}
|
|
2381
|
-
execute(graphics) {
|
|
2382
|
-
if (!graphics.isRenderable)
|
|
2383
|
-
return;
|
|
2384
|
-
const renderer = this.renderer;
|
|
2385
|
-
const context = graphics.context;
|
|
2386
|
-
const contextSystem = renderer.graphicsContext;
|
|
2387
|
-
if (!contextSystem.getGpuContext(context).batches.length) {
|
|
2388
|
-
return;
|
|
2389
|
-
}
|
|
2390
|
-
const shader = context.customShader || this._adaptor.shader;
|
|
2391
|
-
this.state.blendMode = graphics.groupBlendMode;
|
|
2392
|
-
const localUniforms = shader.resources.localUniforms.uniforms;
|
|
2393
|
-
localUniforms.uTransformMatrix = graphics.groupTransform;
|
|
2394
|
-
localUniforms.uRound = renderer._roundPixels | graphics._roundPixels;
|
|
2395
|
-
color32BitToUniform(
|
|
2396
|
-
graphics.groupColorAlpha,
|
|
2397
|
-
localUniforms.uColor,
|
|
2398
|
-
0
|
|
2399
|
-
);
|
|
2400
|
-
this._adaptor.execute(this, graphics);
|
|
2401
|
-
}
|
|
2402
|
-
_rebuild(graphics) {
|
|
2403
|
-
const gpuData = this._getGpuDataForRenderable(graphics);
|
|
2404
|
-
const gpuContext = this.renderer.graphicsContext.updateGpuContext(graphics.context);
|
|
2405
|
-
gpuData.destroy();
|
|
2406
|
-
if (gpuContext.isBatchable) {
|
|
2407
|
-
this._updateBatchesForRenderable(graphics, gpuData);
|
|
2408
|
-
}
|
|
2409
|
-
}
|
|
2410
|
-
_addToBatcher(graphics, instructionSet) {
|
|
2411
|
-
const batchPipe = this.renderer.renderPipes.batch;
|
|
2412
|
-
const batches = this._getGpuDataForRenderable(graphics).batches;
|
|
2413
|
-
for (let i = 0; i < batches.length; i++) {
|
|
2414
|
-
const batch = batches[i];
|
|
2415
|
-
batchPipe.addToBatch(batch, instructionSet);
|
|
2416
|
-
}
|
|
2417
|
-
}
|
|
2418
|
-
_getGpuDataForRenderable(graphics) {
|
|
2419
|
-
return graphics._gpuData[this.renderer.uid] || this._initGpuDataForRenderable(graphics);
|
|
2420
|
-
}
|
|
2421
|
-
_initGpuDataForRenderable(graphics) {
|
|
2422
|
-
const gpuData = new GraphicsGpuData();
|
|
2423
|
-
graphics._gpuData[this.renderer.uid] = gpuData;
|
|
2424
|
-
return gpuData;
|
|
2425
|
-
}
|
|
2426
|
-
_updateBatchesForRenderable(graphics, gpuData) {
|
|
2427
|
-
const context = graphics.context;
|
|
2428
|
-
const gpuContext = this.renderer.graphicsContext.getGpuContext(context);
|
|
2429
|
-
const roundPixels = this.renderer._roundPixels | graphics._roundPixels;
|
|
2430
|
-
gpuData.batches = gpuContext.batches.map((batch) => {
|
|
2431
|
-
const batchClone = BigPool.get(BatchableGraphics);
|
|
2432
|
-
batch.copyTo(batchClone);
|
|
2433
|
-
batchClone.renderable = graphics;
|
|
2434
|
-
batchClone.roundPixels = roundPixels;
|
|
2435
|
-
return batchClone;
|
|
2436
|
-
});
|
|
2437
|
-
}
|
|
2438
|
-
destroy() {
|
|
2439
|
-
this.renderer = null;
|
|
2440
|
-
this._adaptor.destroy();
|
|
2441
|
-
this._adaptor = null;
|
|
2442
|
-
this.state = null;
|
|
2443
|
-
}
|
|
2444
|
-
}
|
|
2445
|
-
/** @ignore */
|
|
2446
|
-
GraphicsPipe.extension = {
|
|
2447
|
-
type: [
|
|
2448
|
-
ExtensionType.WebGLPipes,
|
|
2449
|
-
ExtensionType.WebGPUPipes,
|
|
2450
|
-
ExtensionType.CanvasPipes
|
|
2451
|
-
],
|
|
2452
|
-
name: "graphics"
|
|
2453
|
-
};
|
|
2454
|
-
|
|
2455
|
-
const _PlaneGeometry = class _PlaneGeometry extends MeshGeometry {
|
|
2456
|
-
constructor(...args) {
|
|
2457
|
-
super({});
|
|
2458
|
-
let options = args[0] ?? {};
|
|
2459
|
-
if (typeof options === "number") {
|
|
2460
|
-
deprecation(v8_0_0, "PlaneGeometry constructor changed please use { width, height, verticesX, verticesY } instead");
|
|
2461
|
-
options = {
|
|
2462
|
-
width: options,
|
|
2463
|
-
height: args[1],
|
|
2464
|
-
verticesX: args[2],
|
|
2465
|
-
verticesY: args[3]
|
|
2466
|
-
};
|
|
2467
|
-
}
|
|
2468
|
-
this.build(options);
|
|
2469
|
-
}
|
|
2470
|
-
/**
|
|
2471
|
-
* Refreshes plane coordinates
|
|
2472
|
-
* @param options - Options to be applied to plane geometry
|
|
2473
|
-
*/
|
|
2474
|
-
build(options) {
|
|
2475
|
-
options = { ..._PlaneGeometry.defaultOptions, ...options };
|
|
2476
|
-
this.verticesX = this.verticesX ?? options.verticesX;
|
|
2477
|
-
this.verticesY = this.verticesY ?? options.verticesY;
|
|
2478
|
-
this.width = this.width ?? options.width;
|
|
2479
|
-
this.height = this.height ?? options.height;
|
|
2480
|
-
const total = this.verticesX * this.verticesY;
|
|
2481
|
-
const verts = [];
|
|
2482
|
-
const uvs = [];
|
|
2483
|
-
const indices = [];
|
|
2484
|
-
const verticesX = this.verticesX - 1;
|
|
2485
|
-
const verticesY = this.verticesY - 1;
|
|
2486
|
-
const sizeX = this.width / verticesX;
|
|
2487
|
-
const sizeY = this.height / verticesY;
|
|
2488
|
-
for (let i = 0; i < total; i++) {
|
|
2489
|
-
const x = i % this.verticesX;
|
|
2490
|
-
const y = i / this.verticesX | 0;
|
|
2491
|
-
verts.push(x * sizeX, y * sizeY);
|
|
2492
|
-
uvs.push(x / verticesX, y / verticesY);
|
|
2493
|
-
}
|
|
2494
|
-
const totalSub = verticesX * verticesY;
|
|
2495
|
-
for (let i = 0; i < totalSub; i++) {
|
|
2496
|
-
const xpos = i % verticesX;
|
|
2497
|
-
const ypos = i / verticesX | 0;
|
|
2498
|
-
const value = ypos * this.verticesX + xpos;
|
|
2499
|
-
const value2 = ypos * this.verticesX + xpos + 1;
|
|
2500
|
-
const value3 = (ypos + 1) * this.verticesX + xpos;
|
|
2501
|
-
const value4 = (ypos + 1) * this.verticesX + xpos + 1;
|
|
2502
|
-
indices.push(
|
|
2503
|
-
value,
|
|
2504
|
-
value2,
|
|
2505
|
-
value3,
|
|
2506
|
-
value2,
|
|
2507
|
-
value4,
|
|
2508
|
-
value3
|
|
2509
|
-
);
|
|
2510
|
-
}
|
|
2511
|
-
this.buffers[0].data = new Float32Array(verts);
|
|
2512
|
-
this.buffers[1].data = new Float32Array(uvs);
|
|
2513
|
-
this.indexBuffer.data = new Uint32Array(indices);
|
|
2514
|
-
this.buffers[0].update();
|
|
2515
|
-
this.buffers[1].update();
|
|
2516
|
-
this.indexBuffer.update();
|
|
2517
|
-
}
|
|
2518
|
-
};
|
|
2519
|
-
_PlaneGeometry.defaultOptions = {
|
|
2520
|
-
width: 100,
|
|
2521
|
-
height: 100,
|
|
2522
|
-
verticesX: 10,
|
|
2523
|
-
verticesY: 10
|
|
2524
|
-
};
|
|
2525
|
-
let PlaneGeometry = _PlaneGeometry;
|
|
2526
|
-
|
|
2527
|
-
class BatchableMesh {
|
|
2528
|
-
constructor() {
|
|
2529
|
-
this.batcherName = "default";
|
|
2530
|
-
this.packAsQuad = false;
|
|
2531
|
-
this.indexOffset = 0;
|
|
2532
|
-
this.attributeOffset = 0;
|
|
2533
|
-
this.roundPixels = 0;
|
|
2534
|
-
this._batcher = null;
|
|
2535
|
-
this._batch = null;
|
|
2536
|
-
this._textureMatrixUpdateId = -1;
|
|
2537
|
-
this._uvUpdateId = -1;
|
|
2538
|
-
}
|
|
2539
|
-
get blendMode() {
|
|
2540
|
-
return this.renderable.groupBlendMode;
|
|
2541
|
-
}
|
|
2542
|
-
get topology() {
|
|
2543
|
-
return this._topology || this.geometry.topology;
|
|
2544
|
-
}
|
|
2545
|
-
set topology(value) {
|
|
2546
|
-
this._topology = value;
|
|
2547
|
-
}
|
|
2548
|
-
reset() {
|
|
2549
|
-
this.renderable = null;
|
|
2550
|
-
this.texture = null;
|
|
2551
|
-
this._batcher = null;
|
|
2552
|
-
this._batch = null;
|
|
2553
|
-
this.geometry = null;
|
|
2554
|
-
this._uvUpdateId = -1;
|
|
2555
|
-
this._textureMatrixUpdateId = -1;
|
|
2556
|
-
}
|
|
2557
|
-
/**
|
|
2558
|
-
* Sets the texture for the batchable mesh.
|
|
2559
|
-
* As it does so, it resets the texture matrix update ID.
|
|
2560
|
-
* this is to ensure that the texture matrix is recalculated when the uvs are referenced
|
|
2561
|
-
* @param value - The texture to set.
|
|
2562
|
-
*/
|
|
2563
|
-
setTexture(value) {
|
|
2564
|
-
if (this.texture === value)
|
|
2565
|
-
return;
|
|
2566
|
-
this.texture = value;
|
|
2567
|
-
this._textureMatrixUpdateId = -1;
|
|
2568
|
-
}
|
|
2569
|
-
get uvs() {
|
|
2570
|
-
const geometry = this.geometry;
|
|
2571
|
-
const uvBuffer = geometry.getBuffer("aUV");
|
|
2572
|
-
const uvs = uvBuffer.data;
|
|
2573
|
-
let transformedUvs = uvs;
|
|
2574
|
-
const textureMatrix = this.texture.textureMatrix;
|
|
2575
|
-
if (!textureMatrix.isSimple) {
|
|
2576
|
-
transformedUvs = this._transformedUvs;
|
|
2577
|
-
if (this._textureMatrixUpdateId !== textureMatrix._updateID || this._uvUpdateId !== uvBuffer._updateID) {
|
|
2578
|
-
if (!transformedUvs || transformedUvs.length < uvs.length) {
|
|
2579
|
-
transformedUvs = this._transformedUvs = new Float32Array(uvs.length);
|
|
2580
|
-
}
|
|
2581
|
-
this._textureMatrixUpdateId = textureMatrix._updateID;
|
|
2582
|
-
this._uvUpdateId = uvBuffer._updateID;
|
|
2583
|
-
textureMatrix.multiplyUvs(uvs, transformedUvs);
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
return transformedUvs;
|
|
2587
|
-
}
|
|
2588
|
-
get positions() {
|
|
2589
|
-
return this.geometry.positions;
|
|
2590
|
-
}
|
|
2591
|
-
get indices() {
|
|
2592
|
-
return this.geometry.indices;
|
|
2593
|
-
}
|
|
2594
|
-
get color() {
|
|
2595
|
-
return this.renderable.groupColorAlpha;
|
|
2596
|
-
}
|
|
2597
|
-
get groupTransform() {
|
|
2598
|
-
return this.renderable.groupTransform;
|
|
2599
|
-
}
|
|
2600
|
-
get attributeSize() {
|
|
2601
|
-
return this.geometry.positions.length / 2;
|
|
2602
|
-
}
|
|
2603
|
-
get indexSize() {
|
|
2604
|
-
return this.geometry.indices.length;
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
|
|
2608
|
-
class MeshGpuData {
|
|
2609
|
-
destroy() {
|
|
2610
|
-
}
|
|
2611
|
-
}
|
|
2612
|
-
class MeshPipe {
|
|
2613
|
-
constructor(renderer, adaptor) {
|
|
2614
|
-
this.localUniforms = new UniformGroup({
|
|
2615
|
-
uTransformMatrix: { value: new Matrix(), type: "mat3x3<f32>" },
|
|
2616
|
-
uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4<f32>" },
|
|
2617
|
-
uRound: { value: 0, type: "f32" }
|
|
2618
|
-
});
|
|
2619
|
-
this.localUniformsBindGroup = new BindGroup({
|
|
2620
|
-
0: this.localUniforms
|
|
2621
|
-
});
|
|
2622
|
-
this.renderer = renderer;
|
|
2623
|
-
this._adaptor = adaptor;
|
|
2624
|
-
this._adaptor.init();
|
|
2625
|
-
}
|
|
2626
|
-
validateRenderable(mesh) {
|
|
2627
|
-
const meshData = this._getMeshData(mesh);
|
|
2628
|
-
const wasBatched = meshData.batched;
|
|
2629
|
-
const isBatched = mesh.batched;
|
|
2630
|
-
meshData.batched = isBatched;
|
|
2631
|
-
if (wasBatched !== isBatched) {
|
|
2632
|
-
return true;
|
|
2633
|
-
} else if (isBatched) {
|
|
2634
|
-
const geometry = mesh._geometry;
|
|
2635
|
-
if (geometry.indices.length !== meshData.indexSize || geometry.positions.length !== meshData.vertexSize) {
|
|
2636
|
-
meshData.indexSize = geometry.indices.length;
|
|
2637
|
-
meshData.vertexSize = geometry.positions.length;
|
|
2638
|
-
return true;
|
|
2639
|
-
}
|
|
2640
|
-
const batchableMesh = this._getBatchableMesh(mesh);
|
|
2641
|
-
if (batchableMesh.texture.uid !== mesh._texture.uid) {
|
|
2642
|
-
batchableMesh._textureMatrixUpdateId = -1;
|
|
2643
|
-
}
|
|
2644
|
-
return !batchableMesh._batcher.checkAndUpdateTexture(
|
|
2645
|
-
batchableMesh,
|
|
2646
|
-
mesh._texture
|
|
2647
|
-
);
|
|
2648
|
-
}
|
|
2649
|
-
return false;
|
|
2650
|
-
}
|
|
2651
|
-
addRenderable(mesh, instructionSet) {
|
|
2652
|
-
const batcher = this.renderer.renderPipes.batch;
|
|
2653
|
-
const { batched } = this._getMeshData(mesh);
|
|
2654
|
-
if (batched) {
|
|
2655
|
-
const gpuBatchableMesh = this._getBatchableMesh(mesh);
|
|
2656
|
-
gpuBatchableMesh.setTexture(mesh._texture);
|
|
2657
|
-
gpuBatchableMesh.geometry = mesh._geometry;
|
|
2658
|
-
batcher.addToBatch(gpuBatchableMesh, instructionSet);
|
|
2659
|
-
} else {
|
|
2660
|
-
batcher.break(instructionSet);
|
|
2661
|
-
instructionSet.add(mesh);
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
updateRenderable(mesh) {
|
|
2665
|
-
if (mesh.batched) {
|
|
2666
|
-
const gpuBatchableMesh = this._getBatchableMesh(mesh);
|
|
2667
|
-
gpuBatchableMesh.setTexture(mesh._texture);
|
|
2668
|
-
gpuBatchableMesh.geometry = mesh._geometry;
|
|
2669
|
-
gpuBatchableMesh._batcher.updateElement(gpuBatchableMesh);
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
execute(mesh) {
|
|
2673
|
-
if (!mesh.isRenderable)
|
|
2674
|
-
return;
|
|
2675
|
-
mesh.state.blendMode = getAdjustedBlendModeBlend(mesh.groupBlendMode, mesh.texture._source);
|
|
2676
|
-
const localUniforms = this.localUniforms;
|
|
2677
|
-
localUniforms.uniforms.uTransformMatrix = mesh.groupTransform;
|
|
2678
|
-
localUniforms.uniforms.uRound = this.renderer._roundPixels | mesh._roundPixels;
|
|
2679
|
-
localUniforms.update();
|
|
2680
|
-
color32BitToUniform(
|
|
2681
|
-
mesh.groupColorAlpha,
|
|
2682
|
-
localUniforms.uniforms.uColor,
|
|
2683
|
-
0
|
|
2684
|
-
);
|
|
2685
|
-
this._adaptor.execute(this, mesh);
|
|
2686
|
-
}
|
|
2687
|
-
_getMeshData(mesh) {
|
|
2688
|
-
var _a, _b;
|
|
2689
|
-
(_a = mesh._gpuData)[_b = this.renderer.uid] || (_a[_b] = new MeshGpuData());
|
|
2690
|
-
return mesh._gpuData[this.renderer.uid].meshData || this._initMeshData(mesh);
|
|
2691
|
-
}
|
|
2692
|
-
_initMeshData(mesh) {
|
|
2693
|
-
mesh._gpuData[this.renderer.uid].meshData = {
|
|
2694
|
-
batched: mesh.batched,
|
|
2695
|
-
indexSize: mesh._geometry.indices?.length,
|
|
2696
|
-
vertexSize: mesh._geometry.positions?.length
|
|
2697
|
-
};
|
|
2698
|
-
return mesh._gpuData[this.renderer.uid].meshData;
|
|
2699
|
-
}
|
|
2700
|
-
_getBatchableMesh(mesh) {
|
|
2701
|
-
var _a, _b;
|
|
2702
|
-
(_a = mesh._gpuData)[_b = this.renderer.uid] || (_a[_b] = new MeshGpuData());
|
|
2703
|
-
return mesh._gpuData[this.renderer.uid].batchableMesh || this._initBatchableMesh(mesh);
|
|
2704
|
-
}
|
|
2705
|
-
_initBatchableMesh(mesh) {
|
|
2706
|
-
const gpuMesh = new BatchableMesh();
|
|
2707
|
-
gpuMesh.renderable = mesh;
|
|
2708
|
-
gpuMesh.setTexture(mesh._texture);
|
|
2709
|
-
gpuMesh.transform = mesh.groupTransform;
|
|
2710
|
-
gpuMesh.roundPixels = this.renderer._roundPixels | mesh._roundPixels;
|
|
2711
|
-
mesh._gpuData[this.renderer.uid].batchableMesh = gpuMesh;
|
|
2712
|
-
return gpuMesh;
|
|
2713
|
-
}
|
|
2714
|
-
destroy() {
|
|
2715
|
-
this.localUniforms = null;
|
|
2716
|
-
this.localUniformsBindGroup = null;
|
|
2717
|
-
this._adaptor.destroy();
|
|
2718
|
-
this._adaptor = null;
|
|
2719
|
-
this.renderer = null;
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
/** @ignore */
|
|
2723
|
-
MeshPipe.extension = {
|
|
2724
|
-
type: [
|
|
2725
|
-
ExtensionType.WebGLPipes,
|
|
2726
|
-
ExtensionType.WebGPUPipes,
|
|
2727
|
-
ExtensionType.CanvasPipes
|
|
2728
|
-
],
|
|
2729
|
-
name: "mesh"
|
|
2730
|
-
};
|
|
2731
|
-
|
|
2732
|
-
class GlParticleContainerAdaptor {
|
|
2733
|
-
execute(particleContainerPipe, container) {
|
|
2734
|
-
const state = particleContainerPipe.state;
|
|
2735
|
-
const renderer = particleContainerPipe.renderer;
|
|
2736
|
-
const shader = container.shader || particleContainerPipe.defaultShader;
|
|
2737
|
-
shader.resources.uTexture = container.texture._source;
|
|
2738
|
-
shader.resources.uniforms = particleContainerPipe.localUniforms;
|
|
2739
|
-
const gl = renderer.gl;
|
|
2740
|
-
const buffer = particleContainerPipe.getBuffers(container);
|
|
2741
|
-
renderer.shader.bind(shader);
|
|
2742
|
-
renderer.state.set(state);
|
|
2743
|
-
renderer.geometry.bind(buffer.geometry, shader.glProgram);
|
|
2744
|
-
const byteSize = buffer.geometry.indexBuffer.data.BYTES_PER_ELEMENT;
|
|
2745
|
-
const glType = byteSize === 2 ? gl.UNSIGNED_SHORT : gl.UNSIGNED_INT;
|
|
2746
|
-
gl.drawElements(gl.TRIANGLES, container.particleChildren.length * 6, glType, 0);
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
class GpuParticleContainerAdaptor {
|
|
2751
|
-
execute(particleContainerPipe, container) {
|
|
2752
|
-
const renderer = particleContainerPipe.renderer;
|
|
2753
|
-
const shader = container.shader || particleContainerPipe.defaultShader;
|
|
2754
|
-
shader.groups[0] = renderer.renderPipes.uniformBatch.getUniformBindGroup(particleContainerPipe.localUniforms, true);
|
|
2755
|
-
shader.groups[1] = renderer.texture.getTextureBindGroup(container.texture);
|
|
2756
|
-
const state = particleContainerPipe.state;
|
|
2757
|
-
const buffer = particleContainerPipe.getBuffers(container);
|
|
2758
|
-
renderer.encoder.draw({
|
|
2759
|
-
geometry: buffer.geometry,
|
|
2760
|
-
shader: container.shader || particleContainerPipe.defaultShader,
|
|
2761
|
-
state,
|
|
2762
|
-
size: container.particleChildren.length * 6
|
|
2763
|
-
});
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
|
|
2767
|
-
function createIndicesForQuads(size, outBuffer = null) {
|
|
2768
|
-
const totalIndices = size * 6;
|
|
2769
|
-
if (totalIndices > 65535) {
|
|
2770
|
-
outBuffer || (outBuffer = new Uint32Array(totalIndices));
|
|
2771
|
-
} else {
|
|
2772
|
-
outBuffer || (outBuffer = new Uint16Array(totalIndices));
|
|
2773
|
-
}
|
|
2774
|
-
if (outBuffer.length !== totalIndices) {
|
|
2775
|
-
throw new Error(`Out buffer length is incorrect, got ${outBuffer.length} and expected ${totalIndices}`);
|
|
2776
|
-
}
|
|
2777
|
-
for (let i = 0, j = 0; i < totalIndices; i += 6, j += 4) {
|
|
2778
|
-
outBuffer[i + 0] = j + 0;
|
|
2779
|
-
outBuffer[i + 1] = j + 1;
|
|
2780
|
-
outBuffer[i + 2] = j + 2;
|
|
2781
|
-
outBuffer[i + 3] = j + 0;
|
|
2782
|
-
outBuffer[i + 4] = j + 2;
|
|
2783
|
-
outBuffer[i + 5] = j + 3;
|
|
2784
|
-
}
|
|
2785
|
-
return outBuffer;
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
function generateParticleUpdateFunction(properties) {
|
|
2789
|
-
return {
|
|
2790
|
-
dynamicUpdate: generateUpdateFunction(properties, true),
|
|
2791
|
-
staticUpdate: generateUpdateFunction(properties, false)
|
|
2792
|
-
};
|
|
2793
|
-
}
|
|
2794
|
-
function generateUpdateFunction(properties, dynamic) {
|
|
2795
|
-
const funcFragments = [];
|
|
2796
|
-
funcFragments.push(`
|
|
2797
|
-
|
|
2798
|
-
var index = 0;
|
|
2799
|
-
|
|
2800
|
-
for (let i = 0; i < ps.length; ++i)
|
|
2801
|
-
{
|
|
2802
|
-
const p = ps[i];
|
|
2803
|
-
|
|
2804
|
-
`);
|
|
2805
|
-
let offset = 0;
|
|
2806
|
-
for (const i in properties) {
|
|
2807
|
-
const property = properties[i];
|
|
2808
|
-
if (dynamic !== property.dynamic)
|
|
2809
|
-
continue;
|
|
2810
|
-
funcFragments.push(`offset = index + ${offset}`);
|
|
2811
|
-
funcFragments.push(property.code);
|
|
2812
|
-
const attributeInfo = getAttributeInfoFromFormat(property.format);
|
|
2813
|
-
offset += attributeInfo.stride / 4;
|
|
2814
|
-
}
|
|
2815
|
-
funcFragments.push(`
|
|
2816
|
-
index += stride * 4;
|
|
2817
|
-
}
|
|
2818
|
-
`);
|
|
2819
|
-
funcFragments.unshift(`
|
|
2820
|
-
var stride = ${offset};
|
|
2821
|
-
`);
|
|
2822
|
-
const functionSource = funcFragments.join("\n");
|
|
2823
|
-
return new Function("ps", "f32v", "u32v", functionSource);
|
|
2824
|
-
}
|
|
2825
|
-
|
|
2826
|
-
class ParticleBuffer {
|
|
2827
|
-
constructor(options) {
|
|
2828
|
-
this._size = 0;
|
|
2829
|
-
this._generateParticleUpdateCache = {};
|
|
2830
|
-
const size = this._size = options.size ?? 1e3;
|
|
2831
|
-
const properties = options.properties;
|
|
2832
|
-
let staticVertexSize = 0;
|
|
2833
|
-
let dynamicVertexSize = 0;
|
|
2834
|
-
for (const i in properties) {
|
|
2835
|
-
const property = properties[i];
|
|
2836
|
-
const attributeInfo = getAttributeInfoFromFormat(property.format);
|
|
2837
|
-
if (property.dynamic) {
|
|
2838
|
-
dynamicVertexSize += attributeInfo.stride;
|
|
2839
|
-
} else {
|
|
2840
|
-
staticVertexSize += attributeInfo.stride;
|
|
2841
|
-
}
|
|
2842
|
-
}
|
|
2843
|
-
this._dynamicStride = dynamicVertexSize / 4;
|
|
2844
|
-
this._staticStride = staticVertexSize / 4;
|
|
2845
|
-
this.staticAttributeBuffer = new ViewableBuffer(size * 4 * staticVertexSize);
|
|
2846
|
-
this.dynamicAttributeBuffer = new ViewableBuffer(size * 4 * dynamicVertexSize);
|
|
2847
|
-
this.indexBuffer = createIndicesForQuads(size);
|
|
2848
|
-
const geometry = new Geometry();
|
|
2849
|
-
let dynamicOffset = 0;
|
|
2850
|
-
let staticOffset = 0;
|
|
2851
|
-
this._staticBuffer = new Buffer({
|
|
2852
|
-
data: new Float32Array(1),
|
|
2853
|
-
label: "static-particle-buffer",
|
|
2854
|
-
shrinkToFit: false,
|
|
2855
|
-
usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
|
|
2856
|
-
});
|
|
2857
|
-
this._dynamicBuffer = new Buffer({
|
|
2858
|
-
data: new Float32Array(1),
|
|
2859
|
-
label: "dynamic-particle-buffer",
|
|
2860
|
-
shrinkToFit: false,
|
|
2861
|
-
usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
|
|
2862
|
-
});
|
|
2863
|
-
for (const i in properties) {
|
|
2864
|
-
const property = properties[i];
|
|
2865
|
-
const attributeInfo = getAttributeInfoFromFormat(property.format);
|
|
2866
|
-
if (property.dynamic) {
|
|
2867
|
-
geometry.addAttribute(property.attributeName, {
|
|
2868
|
-
buffer: this._dynamicBuffer,
|
|
2869
|
-
stride: this._dynamicStride * 4,
|
|
2870
|
-
offset: dynamicOffset * 4,
|
|
2871
|
-
format: property.format
|
|
2872
|
-
});
|
|
2873
|
-
dynamicOffset += attributeInfo.size;
|
|
2874
|
-
} else {
|
|
2875
|
-
geometry.addAttribute(property.attributeName, {
|
|
2876
|
-
buffer: this._staticBuffer,
|
|
2877
|
-
stride: this._staticStride * 4,
|
|
2878
|
-
offset: staticOffset * 4,
|
|
2879
|
-
format: property.format
|
|
2880
|
-
});
|
|
2881
|
-
staticOffset += attributeInfo.size;
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
geometry.addIndex(this.indexBuffer);
|
|
2885
|
-
const uploadFunction = this.getParticleUpdate(properties);
|
|
2886
|
-
this._dynamicUpload = uploadFunction.dynamicUpdate;
|
|
2887
|
-
this._staticUpload = uploadFunction.staticUpdate;
|
|
2888
|
-
this.geometry = geometry;
|
|
2889
|
-
}
|
|
2890
|
-
getParticleUpdate(properties) {
|
|
2891
|
-
const key = getParticleSyncKey(properties);
|
|
2892
|
-
if (this._generateParticleUpdateCache[key]) {
|
|
2893
|
-
return this._generateParticleUpdateCache[key];
|
|
2894
|
-
}
|
|
2895
|
-
this._generateParticleUpdateCache[key] = this.generateParticleUpdate(properties);
|
|
2896
|
-
return this._generateParticleUpdateCache[key];
|
|
2897
|
-
}
|
|
2898
|
-
generateParticleUpdate(properties) {
|
|
2899
|
-
return generateParticleUpdateFunction(properties);
|
|
2900
|
-
}
|
|
2901
|
-
update(particles, uploadStatic) {
|
|
2902
|
-
if (particles.length > this._size) {
|
|
2903
|
-
uploadStatic = true;
|
|
2904
|
-
this._size = Math.max(particles.length, this._size * 1.5 | 0);
|
|
2905
|
-
this.staticAttributeBuffer = new ViewableBuffer(this._size * this._staticStride * 4 * 4);
|
|
2906
|
-
this.dynamicAttributeBuffer = new ViewableBuffer(this._size * this._dynamicStride * 4 * 4);
|
|
2907
|
-
this.indexBuffer = createIndicesForQuads(this._size);
|
|
2908
|
-
this.geometry.indexBuffer.setDataWithSize(
|
|
2909
|
-
this.indexBuffer,
|
|
2910
|
-
this.indexBuffer.byteLength,
|
|
2911
|
-
true
|
|
2912
|
-
);
|
|
2913
|
-
}
|
|
2914
|
-
const dynamicAttributeBuffer = this.dynamicAttributeBuffer;
|
|
2915
|
-
this._dynamicUpload(particles, dynamicAttributeBuffer.float32View, dynamicAttributeBuffer.uint32View);
|
|
2916
|
-
this._dynamicBuffer.setDataWithSize(
|
|
2917
|
-
this.dynamicAttributeBuffer.float32View,
|
|
2918
|
-
particles.length * this._dynamicStride * 4,
|
|
2919
|
-
true
|
|
2920
|
-
);
|
|
2921
|
-
if (uploadStatic) {
|
|
2922
|
-
const staticAttributeBuffer = this.staticAttributeBuffer;
|
|
2923
|
-
this._staticUpload(particles, staticAttributeBuffer.float32View, staticAttributeBuffer.uint32View);
|
|
2924
|
-
this._staticBuffer.setDataWithSize(
|
|
2925
|
-
staticAttributeBuffer.float32View,
|
|
2926
|
-
particles.length * this._staticStride * 4,
|
|
2927
|
-
true
|
|
2928
|
-
);
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
destroy() {
|
|
2932
|
-
this._staticBuffer.destroy();
|
|
2933
|
-
this._dynamicBuffer.destroy();
|
|
2934
|
-
this.geometry.destroy();
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
function getParticleSyncKey(properties) {
|
|
2938
|
-
const keyGen = [];
|
|
2939
|
-
for (const key in properties) {
|
|
2940
|
-
const property = properties[key];
|
|
2941
|
-
keyGen.push(key, property.code, property.dynamic ? "d" : "s");
|
|
2942
|
-
}
|
|
2943
|
-
return keyGen.join("_");
|
|
2944
|
-
}
|
|
2945
|
-
|
|
2946
|
-
var fragment = "varying vec2 vUV;\nvarying vec4 vColor;\n\nuniform sampler2D uTexture;\n\nvoid main(void){\n vec4 color = texture2D(uTexture, vUV) * vColor;\n gl_FragColor = color;\n}";
|
|
2947
|
-
|
|
2948
|
-
var vertex = "attribute vec2 aVertex;\nattribute vec2 aUV;\nattribute vec4 aColor;\n\nattribute vec2 aPosition;\nattribute float aRotation;\n\nuniform mat3 uTranslationMatrix;\nuniform float uRound;\nuniform vec2 uResolution;\nuniform vec4 uColor;\n\nvarying vec2 vUV;\nvarying vec4 vColor;\n\nvec2 roundPixels(vec2 position, vec2 targetSize)\n{ \n return (floor(((position * 0.5 + 0.5) * targetSize) + 0.5) / targetSize) * 2.0 - 1.0;\n}\n\nvoid main(void){\n float cosRotation = cos(aRotation);\n float sinRotation = sin(aRotation);\n float x = aVertex.x * cosRotation - aVertex.y * sinRotation;\n float y = aVertex.x * sinRotation + aVertex.y * cosRotation;\n\n vec2 v = vec2(x, y);\n v = v + aPosition;\n\n gl_Position = vec4((uTranslationMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);\n\n if(uRound == 1.0)\n {\n gl_Position.xy = roundPixels(gl_Position.xy, uResolution);\n }\n\n vUV = aUV;\n vColor = vec4(aColor.rgb * aColor.a, aColor.a) * uColor;\n}\n";
|
|
2949
|
-
|
|
2950
|
-
var wgsl = "\nstruct ParticleUniforms {\n uProjectionMatrix:mat3x3<f32>,\n uColor:vec4<f32>,\n uResolution:vec2<f32>,\n uRoundPixels:f32,\n};\n\n@group(0) @binding(0) var<uniform> uniforms: ParticleUniforms;\n\n@group(1) @binding(0) var uTexture: texture_2d<f32>;\n@group(1) @binding(1) var uSampler : sampler;\n\nstruct VSOutput {\n @builtin(position) position: vec4<f32>,\n @location(0) uv : vec2<f32>,\n @location(1) color : vec4<f32>,\n };\n@vertex\nfn mainVertex(\n @location(0) aVertex: vec2<f32>,\n @location(1) aPosition: vec2<f32>,\n @location(2) aUV: vec2<f32>,\n @location(3) aColor: vec4<f32>,\n @location(4) aRotation: f32,\n) -> VSOutput {\n \n let v = vec2(\n aVertex.x * cos(aRotation) - aVertex.y * sin(aRotation),\n aVertex.x * sin(aRotation) + aVertex.y * cos(aRotation)\n ) + aPosition;\n\n let position = vec4((uniforms.uProjectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);\n\n let vColor = vec4(aColor.rgb * aColor.a, aColor.a) * uniforms.uColor;\n\n return VSOutput(\n position,\n aUV,\n vColor,\n );\n}\n\n@fragment\nfn mainFragment(\n @location(0) uv: vec2<f32>,\n @location(1) color: vec4<f32>,\n @builtin(position) position: vec4<f32>,\n) -> @location(0) vec4<f32> {\n\n var sample = textureSample(uTexture, uSampler, uv) * color;\n \n return sample;\n}";
|
|
2951
|
-
|
|
2952
|
-
class ParticleShader extends Shader {
|
|
2953
|
-
constructor() {
|
|
2954
|
-
const glProgram = GlProgram.from({
|
|
2955
|
-
vertex,
|
|
2956
|
-
fragment
|
|
2957
|
-
});
|
|
2958
|
-
const gpuProgram = GpuProgram.from({
|
|
2959
|
-
fragment: {
|
|
2960
|
-
source: wgsl,
|
|
2961
|
-
entryPoint: "mainFragment"
|
|
2962
|
-
},
|
|
2963
|
-
vertex: {
|
|
2964
|
-
source: wgsl,
|
|
2965
|
-
entryPoint: "mainVertex"
|
|
2966
|
-
}
|
|
2967
|
-
});
|
|
2968
|
-
super({
|
|
2969
|
-
glProgram,
|
|
2970
|
-
gpuProgram,
|
|
2971
|
-
resources: {
|
|
2972
|
-
// this will be replaced with the texture from the particle container
|
|
2973
|
-
uTexture: Texture.WHITE.source,
|
|
2974
|
-
// this will be replaced with the texture style from the particle container
|
|
2975
|
-
uSampler: new TextureStyle({}),
|
|
2976
|
-
// this will be replaced with the local uniforms from the particle container
|
|
2977
|
-
uniforms: {
|
|
2978
|
-
uTranslationMatrix: { value: new Matrix(), type: "mat3x3<f32>" },
|
|
2979
|
-
uColor: { value: new Color(16777215), type: "vec4<f32>" },
|
|
2980
|
-
uRound: { value: 1, type: "f32" },
|
|
2981
|
-
uResolution: { value: [0, 0], type: "vec2<f32>" }
|
|
2982
|
-
}
|
|
2983
|
-
}
|
|
2984
|
-
});
|
|
2985
|
-
}
|
|
2986
|
-
}
|
|
2987
|
-
|
|
2988
|
-
class ParticleContainerPipe {
|
|
2989
|
-
/**
|
|
2990
|
-
* @param renderer - The renderer this sprite batch works for.
|
|
2991
|
-
* @param adaptor
|
|
2992
|
-
*/
|
|
2993
|
-
constructor(renderer, adaptor) {
|
|
2994
|
-
/** @internal */
|
|
2995
|
-
this.state = State.for2d();
|
|
2996
|
-
/** Local uniforms that are used for rendering particles. */
|
|
2997
|
-
this.localUniforms = new UniformGroup({
|
|
2998
|
-
uTranslationMatrix: { value: new Matrix(), type: "mat3x3<f32>" },
|
|
2999
|
-
uColor: { value: new Float32Array(4), type: "vec4<f32>" },
|
|
3000
|
-
uRound: { value: 1, type: "f32" },
|
|
3001
|
-
uResolution: { value: [0, 0], type: "vec2<f32>" }
|
|
3002
|
-
});
|
|
3003
|
-
this.renderer = renderer;
|
|
3004
|
-
this.adaptor = adaptor;
|
|
3005
|
-
this.defaultShader = new ParticleShader();
|
|
3006
|
-
this.state = State.for2d();
|
|
3007
|
-
}
|
|
3008
|
-
validateRenderable(_renderable) {
|
|
3009
|
-
return false;
|
|
3010
|
-
}
|
|
3011
|
-
addRenderable(renderable, instructionSet) {
|
|
3012
|
-
this.renderer.renderPipes.batch.break(instructionSet);
|
|
3013
|
-
instructionSet.add(renderable);
|
|
3014
|
-
}
|
|
3015
|
-
getBuffers(renderable) {
|
|
3016
|
-
return renderable._gpuData[this.renderer.uid] || this._initBuffer(renderable);
|
|
3017
|
-
}
|
|
3018
|
-
_initBuffer(renderable) {
|
|
3019
|
-
renderable._gpuData[this.renderer.uid] = new ParticleBuffer({
|
|
3020
|
-
size: renderable.particleChildren.length,
|
|
3021
|
-
properties: renderable._properties
|
|
3022
|
-
});
|
|
3023
|
-
return renderable._gpuData[this.renderer.uid];
|
|
3024
|
-
}
|
|
3025
|
-
updateRenderable(_renderable) {
|
|
3026
|
-
}
|
|
3027
|
-
execute(container) {
|
|
3028
|
-
const children = container.particleChildren;
|
|
3029
|
-
if (children.length === 0) {
|
|
3030
|
-
return;
|
|
3031
|
-
}
|
|
3032
|
-
const renderer = this.renderer;
|
|
3033
|
-
const buffer = this.getBuffers(container);
|
|
3034
|
-
container.texture || (container.texture = children[0].texture);
|
|
3035
|
-
const state = this.state;
|
|
3036
|
-
buffer.update(children, container._childrenDirty);
|
|
3037
|
-
container._childrenDirty = false;
|
|
3038
|
-
state.blendMode = getAdjustedBlendModeBlend(container.blendMode, container.texture._source);
|
|
3039
|
-
const uniforms = this.localUniforms.uniforms;
|
|
3040
|
-
const transformationMatrix = uniforms.uTranslationMatrix;
|
|
3041
|
-
container.worldTransform.copyTo(transformationMatrix);
|
|
3042
|
-
transformationMatrix.prepend(renderer.globalUniforms.globalUniformData.projectionMatrix);
|
|
3043
|
-
uniforms.uResolution = renderer.globalUniforms.globalUniformData.resolution;
|
|
3044
|
-
uniforms.uRound = renderer._roundPixels | container._roundPixels;
|
|
3045
|
-
color32BitToUniform(
|
|
3046
|
-
container.groupColorAlpha,
|
|
3047
|
-
uniforms.uColor,
|
|
3048
|
-
0
|
|
3049
|
-
);
|
|
3050
|
-
this.adaptor.execute(this, container);
|
|
3051
|
-
}
|
|
3052
|
-
/** Destroys the ParticleRenderer. */
|
|
3053
|
-
destroy() {
|
|
3054
|
-
if (this.defaultShader) {
|
|
3055
|
-
this.defaultShader.destroy();
|
|
3056
|
-
this.defaultShader = null;
|
|
3057
|
-
}
|
|
3058
|
-
}
|
|
3059
|
-
}
|
|
3060
|
-
|
|
3061
|
-
class GlParticleContainerPipe extends ParticleContainerPipe {
|
|
3062
|
-
constructor(renderer) {
|
|
3063
|
-
super(renderer, new GlParticleContainerAdaptor());
|
|
3064
|
-
}
|
|
3065
|
-
}
|
|
3066
|
-
/** @ignore */
|
|
3067
|
-
GlParticleContainerPipe.extension = {
|
|
3068
|
-
type: [
|
|
3069
|
-
ExtensionType.WebGLPipes
|
|
3070
|
-
],
|
|
3071
|
-
name: "particle"
|
|
3072
|
-
};
|
|
3073
|
-
|
|
3074
|
-
class GpuParticleContainerPipe extends ParticleContainerPipe {
|
|
3075
|
-
constructor(renderer) {
|
|
3076
|
-
super(renderer, new GpuParticleContainerAdaptor());
|
|
3077
|
-
}
|
|
3078
|
-
}
|
|
3079
|
-
/** @ignore */
|
|
3080
|
-
GpuParticleContainerPipe.extension = {
|
|
3081
|
-
type: [
|
|
3082
|
-
ExtensionType.WebGPUPipes
|
|
3083
|
-
],
|
|
3084
|
-
name: "particle"
|
|
3085
|
-
};
|
|
3086
|
-
|
|
3087
|
-
const _NineSliceGeometry = class _NineSliceGeometry extends PlaneGeometry {
|
|
3088
|
-
constructor(options = {}) {
|
|
3089
|
-
options = { ..._NineSliceGeometry.defaultOptions, ...options };
|
|
3090
|
-
super({
|
|
3091
|
-
width: options.width,
|
|
3092
|
-
height: options.height,
|
|
3093
|
-
verticesX: 4,
|
|
3094
|
-
verticesY: 4
|
|
3095
|
-
});
|
|
3096
|
-
this.update(options);
|
|
3097
|
-
}
|
|
3098
|
-
/**
|
|
3099
|
-
* Updates the NineSliceGeometry with the options.
|
|
3100
|
-
* @param options - The options of the NineSliceGeometry.
|
|
3101
|
-
*/
|
|
3102
|
-
update(options) {
|
|
3103
|
-
this.width = options.width ?? this.width;
|
|
3104
|
-
this.height = options.height ?? this.height;
|
|
3105
|
-
this._originalWidth = options.originalWidth ?? this._originalWidth;
|
|
3106
|
-
this._originalHeight = options.originalHeight ?? this._originalHeight;
|
|
3107
|
-
this._leftWidth = options.leftWidth ?? this._leftWidth;
|
|
3108
|
-
this._rightWidth = options.rightWidth ?? this._rightWidth;
|
|
3109
|
-
this._topHeight = options.topHeight ?? this._topHeight;
|
|
3110
|
-
this._bottomHeight = options.bottomHeight ?? this._bottomHeight;
|
|
3111
|
-
this._anchorX = options.anchor?.x;
|
|
3112
|
-
this._anchorY = options.anchor?.y;
|
|
3113
|
-
this.updateUvs();
|
|
3114
|
-
this.updatePositions();
|
|
3115
|
-
}
|
|
3116
|
-
/** Updates the positions of the vertices. */
|
|
3117
|
-
updatePositions() {
|
|
3118
|
-
const p = this.positions;
|
|
3119
|
-
const {
|
|
3120
|
-
width,
|
|
3121
|
-
height,
|
|
3122
|
-
_leftWidth,
|
|
3123
|
-
_rightWidth,
|
|
3124
|
-
_topHeight,
|
|
3125
|
-
_bottomHeight,
|
|
3126
|
-
_anchorX,
|
|
3127
|
-
_anchorY
|
|
3128
|
-
} = this;
|
|
3129
|
-
const w = _leftWidth + _rightWidth;
|
|
3130
|
-
const scaleW = width > w ? 1 : width / w;
|
|
3131
|
-
const h = _topHeight + _bottomHeight;
|
|
3132
|
-
const scaleH = height > h ? 1 : height / h;
|
|
3133
|
-
const scale = Math.min(scaleW, scaleH);
|
|
3134
|
-
const anchorOffsetX = _anchorX * width;
|
|
3135
|
-
const anchorOffsetY = _anchorY * height;
|
|
3136
|
-
p[0] = p[8] = p[16] = p[24] = -anchorOffsetX;
|
|
3137
|
-
p[2] = p[10] = p[18] = p[26] = _leftWidth * scale - anchorOffsetX;
|
|
3138
|
-
p[4] = p[12] = p[20] = p[28] = width - _rightWidth * scale - anchorOffsetX;
|
|
3139
|
-
p[6] = p[14] = p[22] = p[30] = width - anchorOffsetX;
|
|
3140
|
-
p[1] = p[3] = p[5] = p[7] = -anchorOffsetY;
|
|
3141
|
-
p[9] = p[11] = p[13] = p[15] = _topHeight * scale - anchorOffsetY;
|
|
3142
|
-
p[17] = p[19] = p[21] = p[23] = height - _bottomHeight * scale - anchorOffsetY;
|
|
3143
|
-
p[25] = p[27] = p[29] = p[31] = height - anchorOffsetY;
|
|
3144
|
-
this.getBuffer("aPosition").update();
|
|
3145
|
-
}
|
|
3146
|
-
/** Updates the UVs of the vertices. */
|
|
3147
|
-
updateUvs() {
|
|
3148
|
-
const uvs = this.uvs;
|
|
3149
|
-
uvs[0] = uvs[8] = uvs[16] = uvs[24] = 0;
|
|
3150
|
-
uvs[1] = uvs[3] = uvs[5] = uvs[7] = 0;
|
|
3151
|
-
uvs[6] = uvs[14] = uvs[22] = uvs[30] = 1;
|
|
3152
|
-
uvs[25] = uvs[27] = uvs[29] = uvs[31] = 1;
|
|
3153
|
-
const _uvw = 1 / this._originalWidth;
|
|
3154
|
-
const _uvh = 1 / this._originalHeight;
|
|
3155
|
-
uvs[2] = uvs[10] = uvs[18] = uvs[26] = _uvw * this._leftWidth;
|
|
3156
|
-
uvs[9] = uvs[11] = uvs[13] = uvs[15] = _uvh * this._topHeight;
|
|
3157
|
-
uvs[4] = uvs[12] = uvs[20] = uvs[28] = 1 - _uvw * this._rightWidth;
|
|
3158
|
-
uvs[17] = uvs[19] = uvs[21] = uvs[23] = 1 - _uvh * this._bottomHeight;
|
|
3159
|
-
this.getBuffer("aUV").update();
|
|
3160
|
-
}
|
|
3161
|
-
};
|
|
3162
|
-
/** The default options for the NineSliceGeometry. */
|
|
3163
|
-
_NineSliceGeometry.defaultOptions = {
|
|
3164
|
-
/** The width of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane. */
|
|
3165
|
-
width: 100,
|
|
3166
|
-
/** The height of the NineSlicePlane, setting this will actually modify the vertices and UV's of this plane. */
|
|
3167
|
-
height: 100,
|
|
3168
|
-
/** The width of the left column. */
|
|
3169
|
-
leftWidth: 10,
|
|
3170
|
-
/** The height of the top row. */
|
|
3171
|
-
topHeight: 10,
|
|
3172
|
-
/** The width of the right column. */
|
|
3173
|
-
rightWidth: 10,
|
|
3174
|
-
/** The height of the bottom row. */
|
|
3175
|
-
bottomHeight: 10,
|
|
3176
|
-
/** The original width of the texture */
|
|
3177
|
-
originalWidth: 100,
|
|
3178
|
-
/** The original height of the texture */
|
|
3179
|
-
originalHeight: 100
|
|
3180
|
-
};
|
|
3181
|
-
let NineSliceGeometry = _NineSliceGeometry;
|
|
3182
|
-
|
|
3183
|
-
class NineSliceSpriteGpuData extends BatchableMesh {
|
|
3184
|
-
constructor() {
|
|
3185
|
-
super();
|
|
3186
|
-
this.geometry = new NineSliceGeometry();
|
|
3187
|
-
}
|
|
3188
|
-
destroy() {
|
|
3189
|
-
this.geometry.destroy();
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
class NineSliceSpritePipe {
|
|
3193
|
-
constructor(renderer) {
|
|
3194
|
-
this._renderer = renderer;
|
|
3195
|
-
}
|
|
3196
|
-
addRenderable(sprite, instructionSet) {
|
|
3197
|
-
const gpuSprite = this._getGpuSprite(sprite);
|
|
3198
|
-
if (sprite.didViewUpdate)
|
|
3199
|
-
this._updateBatchableSprite(sprite, gpuSprite);
|
|
3200
|
-
this._renderer.renderPipes.batch.addToBatch(gpuSprite, instructionSet);
|
|
3201
|
-
}
|
|
3202
|
-
updateRenderable(sprite) {
|
|
3203
|
-
const gpuSprite = this._getGpuSprite(sprite);
|
|
3204
|
-
if (sprite.didViewUpdate)
|
|
3205
|
-
this._updateBatchableSprite(sprite, gpuSprite);
|
|
3206
|
-
gpuSprite._batcher.updateElement(gpuSprite);
|
|
3207
|
-
}
|
|
3208
|
-
validateRenderable(sprite) {
|
|
3209
|
-
const gpuSprite = this._getGpuSprite(sprite);
|
|
3210
|
-
return !gpuSprite._batcher.checkAndUpdateTexture(
|
|
3211
|
-
gpuSprite,
|
|
3212
|
-
sprite._texture
|
|
3213
|
-
);
|
|
3214
|
-
}
|
|
3215
|
-
_updateBatchableSprite(sprite, batchableSprite) {
|
|
3216
|
-
batchableSprite.geometry.update(sprite);
|
|
3217
|
-
batchableSprite.setTexture(sprite._texture);
|
|
3218
|
-
}
|
|
3219
|
-
_getGpuSprite(sprite) {
|
|
3220
|
-
return sprite._gpuData[this._renderer.uid] || this._initGPUSprite(sprite);
|
|
3221
|
-
}
|
|
3222
|
-
_initGPUSprite(sprite) {
|
|
3223
|
-
const gpuData = sprite._gpuData[this._renderer.uid] = new NineSliceSpriteGpuData();
|
|
3224
|
-
const batchableMesh = gpuData;
|
|
3225
|
-
batchableMesh.renderable = sprite;
|
|
3226
|
-
batchableMesh.transform = sprite.groupTransform;
|
|
3227
|
-
batchableMesh.texture = sprite._texture;
|
|
3228
|
-
batchableMesh.roundPixels = this._renderer._roundPixels | sprite._roundPixels;
|
|
3229
|
-
if (!sprite.didViewUpdate) {
|
|
3230
|
-
this._updateBatchableSprite(sprite, batchableMesh);
|
|
3231
|
-
}
|
|
3232
|
-
return gpuData;
|
|
3233
|
-
}
|
|
3234
|
-
destroy() {
|
|
3235
|
-
this._renderer = null;
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
/** @ignore */
|
|
3239
|
-
NineSliceSpritePipe.extension = {
|
|
3240
|
-
type: [
|
|
3241
|
-
ExtensionType.WebGLPipes,
|
|
3242
|
-
ExtensionType.WebGPUPipes,
|
|
3243
|
-
ExtensionType.CanvasPipes
|
|
3244
|
-
],
|
|
3245
|
-
name: "nineSliceSprite"
|
|
3246
|
-
};
|
|
3247
|
-
|
|
3248
|
-
const tilingBit = {
|
|
3249
|
-
name: "tiling-bit",
|
|
3250
|
-
vertex: {
|
|
3251
|
-
header: (
|
|
3252
|
-
/* wgsl */
|
|
3253
|
-
`
|
|
3254
|
-
struct TilingUniforms {
|
|
3255
|
-
uMapCoord:mat3x3<f32>,
|
|
3256
|
-
uClampFrame:vec4<f32>,
|
|
3257
|
-
uClampOffset:vec2<f32>,
|
|
3258
|
-
uTextureTransform:mat3x3<f32>,
|
|
3259
|
-
uSizeAnchor:vec4<f32>
|
|
3260
|
-
};
|
|
3261
|
-
|
|
3262
|
-
@group(2) @binding(0) var<uniform> tilingUniforms: TilingUniforms;
|
|
3263
|
-
@group(2) @binding(1) var uTexture: texture_2d<f32>;
|
|
3264
|
-
@group(2) @binding(2) var uSampler: sampler;
|
|
3265
|
-
`
|
|
3266
|
-
),
|
|
3267
|
-
main: (
|
|
3268
|
-
/* wgsl */
|
|
3269
|
-
`
|
|
3270
|
-
uv = (tilingUniforms.uTextureTransform * vec3(uv, 1.0)).xy;
|
|
3271
|
-
|
|
3272
|
-
position = (position - tilingUniforms.uSizeAnchor.zw) * tilingUniforms.uSizeAnchor.xy;
|
|
3273
|
-
`
|
|
3274
|
-
)
|
|
3275
|
-
},
|
|
3276
|
-
fragment: {
|
|
3277
|
-
header: (
|
|
3278
|
-
/* wgsl */
|
|
3279
|
-
`
|
|
3280
|
-
struct TilingUniforms {
|
|
3281
|
-
uMapCoord:mat3x3<f32>,
|
|
3282
|
-
uClampFrame:vec4<f32>,
|
|
3283
|
-
uClampOffset:vec2<f32>,
|
|
3284
|
-
uTextureTransform:mat3x3<f32>,
|
|
3285
|
-
uSizeAnchor:vec4<f32>
|
|
3286
|
-
};
|
|
3287
|
-
|
|
3288
|
-
@group(2) @binding(0) var<uniform> tilingUniforms: TilingUniforms;
|
|
3289
|
-
@group(2) @binding(1) var uTexture: texture_2d<f32>;
|
|
3290
|
-
@group(2) @binding(2) var uSampler: sampler;
|
|
3291
|
-
`
|
|
3292
|
-
),
|
|
3293
|
-
main: (
|
|
3294
|
-
/* wgsl */
|
|
3295
|
-
`
|
|
3296
|
-
|
|
3297
|
-
var coord = vUV + ceil(tilingUniforms.uClampOffset - vUV);
|
|
3298
|
-
coord = (tilingUniforms.uMapCoord * vec3(coord, 1.0)).xy;
|
|
3299
|
-
var unclamped = coord;
|
|
3300
|
-
coord = clamp(coord, tilingUniforms.uClampFrame.xy, tilingUniforms.uClampFrame.zw);
|
|
3301
|
-
|
|
3302
|
-
var bias = 0.;
|
|
3303
|
-
|
|
3304
|
-
if(unclamped.x == coord.x && unclamped.y == coord.y)
|
|
3305
|
-
{
|
|
3306
|
-
bias = -32.;
|
|
3307
|
-
}
|
|
3308
|
-
|
|
3309
|
-
outColor = textureSampleBias(uTexture, uSampler, coord, bias);
|
|
3310
|
-
`
|
|
3311
|
-
)
|
|
3312
|
-
}
|
|
3313
|
-
};
|
|
3314
|
-
const tilingBitGl = {
|
|
3315
|
-
name: "tiling-bit",
|
|
3316
|
-
vertex: {
|
|
3317
|
-
header: (
|
|
3318
|
-
/* glsl */
|
|
3319
|
-
`
|
|
3320
|
-
uniform mat3 uTextureTransform;
|
|
3321
|
-
uniform vec4 uSizeAnchor;
|
|
3322
|
-
|
|
3323
|
-
`
|
|
3324
|
-
),
|
|
3325
|
-
main: (
|
|
3326
|
-
/* glsl */
|
|
3327
|
-
`
|
|
3328
|
-
uv = (uTextureTransform * vec3(aUV, 1.0)).xy;
|
|
3329
|
-
|
|
3330
|
-
position = (position - uSizeAnchor.zw) * uSizeAnchor.xy;
|
|
3331
|
-
`
|
|
3332
|
-
)
|
|
3333
|
-
},
|
|
3334
|
-
fragment: {
|
|
3335
|
-
header: (
|
|
3336
|
-
/* glsl */
|
|
3337
|
-
`
|
|
3338
|
-
uniform sampler2D uTexture;
|
|
3339
|
-
uniform mat3 uMapCoord;
|
|
3340
|
-
uniform vec4 uClampFrame;
|
|
3341
|
-
uniform vec2 uClampOffset;
|
|
3342
|
-
`
|
|
3343
|
-
),
|
|
3344
|
-
main: (
|
|
3345
|
-
/* glsl */
|
|
3346
|
-
`
|
|
3347
|
-
|
|
3348
|
-
vec2 coord = vUV + ceil(uClampOffset - vUV);
|
|
3349
|
-
coord = (uMapCoord * vec3(coord, 1.0)).xy;
|
|
3350
|
-
vec2 unclamped = coord;
|
|
3351
|
-
coord = clamp(coord, uClampFrame.xy, uClampFrame.zw);
|
|
3352
|
-
|
|
3353
|
-
outColor = texture(uTexture, coord, unclamped == coord ? 0.0 : -32.0);// lod-bias very negative to force lod 0
|
|
3354
|
-
|
|
3355
|
-
`
|
|
3356
|
-
)
|
|
3357
|
-
}
|
|
3358
|
-
};
|
|
3359
|
-
|
|
3360
|
-
let gpuProgram$1;
|
|
3361
|
-
let glProgram$1;
|
|
3362
|
-
class TilingSpriteShader extends Shader {
|
|
3363
|
-
constructor() {
|
|
3364
|
-
gpuProgram$1 ?? (gpuProgram$1 = compileHighShaderGpuProgram({
|
|
3365
|
-
name: "tiling-sprite-shader",
|
|
3366
|
-
bits: [
|
|
3367
|
-
localUniformBit,
|
|
3368
|
-
tilingBit,
|
|
3369
|
-
roundPixelsBit
|
|
3370
|
-
]
|
|
3371
|
-
}));
|
|
3372
|
-
glProgram$1 ?? (glProgram$1 = compileHighShaderGlProgram({
|
|
3373
|
-
name: "tiling-sprite-shader",
|
|
3374
|
-
bits: [
|
|
3375
|
-
localUniformBitGl,
|
|
3376
|
-
tilingBitGl,
|
|
3377
|
-
roundPixelsBitGl
|
|
3378
|
-
]
|
|
3379
|
-
}));
|
|
3380
|
-
const tilingUniforms = new UniformGroup({
|
|
3381
|
-
uMapCoord: { value: new Matrix(), type: "mat3x3<f32>" },
|
|
3382
|
-
uClampFrame: { value: new Float32Array([0, 0, 1, 1]), type: "vec4<f32>" },
|
|
3383
|
-
uClampOffset: { value: new Float32Array([0, 0]), type: "vec2<f32>" },
|
|
3384
|
-
uTextureTransform: { value: new Matrix(), type: "mat3x3<f32>" },
|
|
3385
|
-
uSizeAnchor: { value: new Float32Array([100, 100, 0.5, 0.5]), type: "vec4<f32>" }
|
|
3386
|
-
});
|
|
3387
|
-
super({
|
|
3388
|
-
glProgram: glProgram$1,
|
|
3389
|
-
gpuProgram: gpuProgram$1,
|
|
3390
|
-
resources: {
|
|
3391
|
-
localUniforms: new UniformGroup({
|
|
3392
|
-
uTransformMatrix: { value: new Matrix(), type: "mat3x3<f32>" },
|
|
3393
|
-
uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4<f32>" },
|
|
3394
|
-
uRound: { value: 0, type: "f32" }
|
|
3395
|
-
}),
|
|
3396
|
-
tilingUniforms,
|
|
3397
|
-
uTexture: Texture.EMPTY.source,
|
|
3398
|
-
uSampler: Texture.EMPTY.source.style
|
|
3399
|
-
}
|
|
3400
|
-
});
|
|
3401
|
-
}
|
|
3402
|
-
updateUniforms(width, height, matrix, anchorX, anchorY, texture) {
|
|
3403
|
-
const tilingUniforms = this.resources.tilingUniforms;
|
|
3404
|
-
const textureWidth = texture.width;
|
|
3405
|
-
const textureHeight = texture.height;
|
|
3406
|
-
const textureMatrix = texture.textureMatrix;
|
|
3407
|
-
const uTextureTransform = tilingUniforms.uniforms.uTextureTransform;
|
|
3408
|
-
uTextureTransform.set(
|
|
3409
|
-
matrix.a * textureWidth / width,
|
|
3410
|
-
matrix.b * textureWidth / height,
|
|
3411
|
-
matrix.c * textureHeight / width,
|
|
3412
|
-
matrix.d * textureHeight / height,
|
|
3413
|
-
matrix.tx / width,
|
|
3414
|
-
matrix.ty / height
|
|
3415
|
-
);
|
|
3416
|
-
uTextureTransform.invert();
|
|
3417
|
-
tilingUniforms.uniforms.uMapCoord = textureMatrix.mapCoord;
|
|
3418
|
-
tilingUniforms.uniforms.uClampFrame = textureMatrix.uClampFrame;
|
|
3419
|
-
tilingUniforms.uniforms.uClampOffset = textureMatrix.uClampOffset;
|
|
3420
|
-
tilingUniforms.uniforms.uTextureTransform = uTextureTransform;
|
|
3421
|
-
tilingUniforms.uniforms.uSizeAnchor[0] = width;
|
|
3422
|
-
tilingUniforms.uniforms.uSizeAnchor[1] = height;
|
|
3423
|
-
tilingUniforms.uniforms.uSizeAnchor[2] = anchorX;
|
|
3424
|
-
tilingUniforms.uniforms.uSizeAnchor[3] = anchorY;
|
|
3425
|
-
if (texture) {
|
|
3426
|
-
this.resources.uTexture = texture.source;
|
|
3427
|
-
this.resources.uSampler = texture.source.style;
|
|
3428
|
-
}
|
|
3429
|
-
}
|
|
3430
|
-
}
|
|
3431
|
-
|
|
3432
|
-
class QuadGeometry extends MeshGeometry {
|
|
3433
|
-
constructor() {
|
|
3434
|
-
super({
|
|
3435
|
-
positions: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
|
|
3436
|
-
uvs: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
|
|
3437
|
-
indices: new Uint32Array([0, 1, 2, 0, 2, 3])
|
|
3438
|
-
});
|
|
3439
|
-
}
|
|
3440
|
-
}
|
|
3441
|
-
|
|
3442
|
-
function setPositions(tilingSprite, positions) {
|
|
3443
|
-
const anchorX = tilingSprite.anchor.x;
|
|
3444
|
-
const anchorY = tilingSprite.anchor.y;
|
|
3445
|
-
positions[0] = -anchorX * tilingSprite.width;
|
|
3446
|
-
positions[1] = -anchorY * tilingSprite.height;
|
|
3447
|
-
positions[2] = (1 - anchorX) * tilingSprite.width;
|
|
3448
|
-
positions[3] = -anchorY * tilingSprite.height;
|
|
3449
|
-
positions[4] = (1 - anchorX) * tilingSprite.width;
|
|
3450
|
-
positions[5] = (1 - anchorY) * tilingSprite.height;
|
|
3451
|
-
positions[6] = -anchorX * tilingSprite.width;
|
|
3452
|
-
positions[7] = (1 - anchorY) * tilingSprite.height;
|
|
3453
|
-
}
|
|
3454
|
-
|
|
3455
|
-
function applyMatrix(array, stride, offset, matrix) {
|
|
3456
|
-
let index = 0;
|
|
3457
|
-
const size = array.length / (stride );
|
|
3458
|
-
const a = matrix.a;
|
|
3459
|
-
const b = matrix.b;
|
|
3460
|
-
const c = matrix.c;
|
|
3461
|
-
const d = matrix.d;
|
|
3462
|
-
const tx = matrix.tx;
|
|
3463
|
-
const ty = matrix.ty;
|
|
3464
|
-
offset *= stride;
|
|
3465
|
-
while (index < size) {
|
|
3466
|
-
const x = array[offset];
|
|
3467
|
-
const y = array[offset + 1];
|
|
3468
|
-
array[offset] = a * x + c * y + tx;
|
|
3469
|
-
array[offset + 1] = b * x + d * y + ty;
|
|
3470
|
-
offset += stride;
|
|
3471
|
-
index++;
|
|
3472
|
-
}
|
|
3473
|
-
}
|
|
3474
|
-
|
|
3475
|
-
function setUvs(tilingSprite, uvs) {
|
|
3476
|
-
const texture = tilingSprite.texture;
|
|
3477
|
-
const width = texture.frame.width;
|
|
3478
|
-
const height = texture.frame.height;
|
|
3479
|
-
let anchorX = 0;
|
|
3480
|
-
let anchorY = 0;
|
|
3481
|
-
if (tilingSprite.applyAnchorToTexture) {
|
|
3482
|
-
anchorX = tilingSprite.anchor.x;
|
|
3483
|
-
anchorY = tilingSprite.anchor.y;
|
|
3484
|
-
}
|
|
3485
|
-
uvs[0] = uvs[6] = -anchorX;
|
|
3486
|
-
uvs[2] = uvs[4] = 1 - anchorX;
|
|
3487
|
-
uvs[1] = uvs[3] = -anchorY;
|
|
3488
|
-
uvs[5] = uvs[7] = 1 - anchorY;
|
|
3489
|
-
const textureMatrix = Matrix.shared;
|
|
3490
|
-
textureMatrix.copyFrom(tilingSprite._tileTransform.matrix);
|
|
3491
|
-
textureMatrix.tx /= tilingSprite.width;
|
|
3492
|
-
textureMatrix.ty /= tilingSprite.height;
|
|
3493
|
-
textureMatrix.invert();
|
|
3494
|
-
textureMatrix.scale(tilingSprite.width / width, tilingSprite.height / height);
|
|
3495
|
-
applyMatrix(uvs, 2, 0, textureMatrix);
|
|
3496
|
-
}
|
|
3497
|
-
|
|
3498
|
-
const sharedQuad = new QuadGeometry();
|
|
3499
|
-
class TilingSpriteGpuData {
|
|
3500
|
-
constructor() {
|
|
3501
|
-
this.canBatch = true;
|
|
3502
|
-
this.geometry = new MeshGeometry({
|
|
3503
|
-
indices: sharedQuad.indices.slice(),
|
|
3504
|
-
positions: sharedQuad.positions.slice(),
|
|
3505
|
-
uvs: sharedQuad.uvs.slice()
|
|
3506
|
-
});
|
|
3507
|
-
}
|
|
3508
|
-
destroy() {
|
|
3509
|
-
this.geometry.destroy();
|
|
3510
|
-
this.shader?.destroy();
|
|
3511
|
-
}
|
|
3512
|
-
}
|
|
3513
|
-
class TilingSpritePipe {
|
|
3514
|
-
constructor(renderer) {
|
|
3515
|
-
this._state = State.default2d;
|
|
3516
|
-
this._renderer = renderer;
|
|
3517
|
-
}
|
|
3518
|
-
validateRenderable(renderable) {
|
|
3519
|
-
const tilingSpriteData = this._getTilingSpriteData(renderable);
|
|
3520
|
-
const couldBatch = tilingSpriteData.canBatch;
|
|
3521
|
-
this._updateCanBatch(renderable);
|
|
3522
|
-
const canBatch = tilingSpriteData.canBatch;
|
|
3523
|
-
if (canBatch && canBatch === couldBatch) {
|
|
3524
|
-
const { batchableMesh } = tilingSpriteData;
|
|
3525
|
-
return !batchableMesh._batcher.checkAndUpdateTexture(
|
|
3526
|
-
batchableMesh,
|
|
3527
|
-
renderable.texture
|
|
3528
|
-
);
|
|
3529
|
-
}
|
|
3530
|
-
return couldBatch !== canBatch;
|
|
3531
|
-
}
|
|
3532
|
-
addRenderable(tilingSprite, instructionSet) {
|
|
3533
|
-
const batcher = this._renderer.renderPipes.batch;
|
|
3534
|
-
this._updateCanBatch(tilingSprite);
|
|
3535
|
-
const tilingSpriteData = this._getTilingSpriteData(tilingSprite);
|
|
3536
|
-
const { geometry, canBatch } = tilingSpriteData;
|
|
3537
|
-
if (canBatch) {
|
|
3538
|
-
tilingSpriteData.batchableMesh || (tilingSpriteData.batchableMesh = new BatchableMesh());
|
|
3539
|
-
const batchableMesh = tilingSpriteData.batchableMesh;
|
|
3540
|
-
if (tilingSprite.didViewUpdate) {
|
|
3541
|
-
this._updateBatchableMesh(tilingSprite);
|
|
3542
|
-
batchableMesh.geometry = geometry;
|
|
3543
|
-
batchableMesh.renderable = tilingSprite;
|
|
3544
|
-
batchableMesh.transform = tilingSprite.groupTransform;
|
|
3545
|
-
batchableMesh.setTexture(tilingSprite._texture);
|
|
3546
|
-
}
|
|
3547
|
-
batchableMesh.roundPixels = this._renderer._roundPixels | tilingSprite._roundPixels;
|
|
3548
|
-
batcher.addToBatch(batchableMesh, instructionSet);
|
|
3549
|
-
} else {
|
|
3550
|
-
batcher.break(instructionSet);
|
|
3551
|
-
tilingSpriteData.shader || (tilingSpriteData.shader = new TilingSpriteShader());
|
|
3552
|
-
this.updateRenderable(tilingSprite);
|
|
3553
|
-
instructionSet.add(tilingSprite);
|
|
3554
|
-
}
|
|
3555
|
-
}
|
|
3556
|
-
execute(tilingSprite) {
|
|
3557
|
-
const { shader } = this._getTilingSpriteData(tilingSprite);
|
|
3558
|
-
shader.groups[0] = this._renderer.globalUniforms.bindGroup;
|
|
3559
|
-
const localUniforms = shader.resources.localUniforms.uniforms;
|
|
3560
|
-
localUniforms.uTransformMatrix = tilingSprite.groupTransform;
|
|
3561
|
-
localUniforms.uRound = this._renderer._roundPixels | tilingSprite._roundPixels;
|
|
3562
|
-
color32BitToUniform(
|
|
3563
|
-
tilingSprite.groupColorAlpha,
|
|
3564
|
-
localUniforms.uColor,
|
|
3565
|
-
0
|
|
3566
|
-
);
|
|
3567
|
-
this._state.blendMode = getAdjustedBlendModeBlend(tilingSprite.groupBlendMode, tilingSprite.texture._source);
|
|
3568
|
-
this._renderer.encoder.draw({
|
|
3569
|
-
geometry: sharedQuad,
|
|
3570
|
-
shader,
|
|
3571
|
-
state: this._state
|
|
3572
|
-
});
|
|
3573
|
-
}
|
|
3574
|
-
updateRenderable(tilingSprite) {
|
|
3575
|
-
const tilingSpriteData = this._getTilingSpriteData(tilingSprite);
|
|
3576
|
-
const { canBatch } = tilingSpriteData;
|
|
3577
|
-
if (canBatch) {
|
|
3578
|
-
const { batchableMesh } = tilingSpriteData;
|
|
3579
|
-
if (tilingSprite.didViewUpdate)
|
|
3580
|
-
this._updateBatchableMesh(tilingSprite);
|
|
3581
|
-
batchableMesh._batcher.updateElement(batchableMesh);
|
|
3582
|
-
} else if (tilingSprite.didViewUpdate) {
|
|
3583
|
-
const { shader } = tilingSpriteData;
|
|
3584
|
-
shader.updateUniforms(
|
|
3585
|
-
tilingSprite.width,
|
|
3586
|
-
tilingSprite.height,
|
|
3587
|
-
tilingSprite._tileTransform.matrix,
|
|
3588
|
-
tilingSprite.anchor.x,
|
|
3589
|
-
tilingSprite.anchor.y,
|
|
3590
|
-
tilingSprite.texture
|
|
3591
|
-
);
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3594
|
-
_getTilingSpriteData(renderable) {
|
|
3595
|
-
return renderable._gpuData[this._renderer.uid] || this._initTilingSpriteData(renderable);
|
|
3596
|
-
}
|
|
3597
|
-
_initTilingSpriteData(tilingSprite) {
|
|
3598
|
-
const gpuData = new TilingSpriteGpuData();
|
|
3599
|
-
gpuData.renderable = tilingSprite;
|
|
3600
|
-
tilingSprite._gpuData[this._renderer.uid] = gpuData;
|
|
3601
|
-
return gpuData;
|
|
3602
|
-
}
|
|
3603
|
-
_updateBatchableMesh(tilingSprite) {
|
|
3604
|
-
const renderableData = this._getTilingSpriteData(tilingSprite);
|
|
3605
|
-
const { geometry } = renderableData;
|
|
3606
|
-
const style = tilingSprite.texture.source.style;
|
|
3607
|
-
if (style.addressMode !== "repeat") {
|
|
3608
|
-
style.addressMode = "repeat";
|
|
3609
|
-
style.update();
|
|
3610
|
-
}
|
|
3611
|
-
setUvs(tilingSprite, geometry.uvs);
|
|
3612
|
-
setPositions(tilingSprite, geometry.positions);
|
|
3613
|
-
}
|
|
3614
|
-
destroy() {
|
|
3615
|
-
this._renderer = null;
|
|
3616
|
-
}
|
|
3617
|
-
_updateCanBatch(tilingSprite) {
|
|
3618
|
-
const renderableData = this._getTilingSpriteData(tilingSprite);
|
|
3619
|
-
const texture = tilingSprite.texture;
|
|
3620
|
-
let _nonPowOf2wrapping = true;
|
|
3621
|
-
if (this._renderer.type === RendererType.WEBGL) {
|
|
3622
|
-
_nonPowOf2wrapping = this._renderer.context.supports.nonPowOf2wrapping;
|
|
3623
|
-
}
|
|
3624
|
-
renderableData.canBatch = texture.textureMatrix.isSimple && (_nonPowOf2wrapping || texture.source.isPowerOfTwo);
|
|
3625
|
-
return renderableData.canBatch;
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
/** @ignore */
|
|
3629
|
-
TilingSpritePipe.extension = {
|
|
3630
|
-
type: [
|
|
3631
|
-
ExtensionType.WebGLPipes,
|
|
3632
|
-
ExtensionType.WebGPUPipes,
|
|
3633
|
-
ExtensionType.CanvasPipes
|
|
3634
|
-
],
|
|
3635
|
-
name: "tilingSprite"
|
|
3636
|
-
};
|
|
3637
|
-
|
|
3638
|
-
const localUniformMSDFBit = {
|
|
3639
|
-
name: "local-uniform-msdf-bit",
|
|
3640
|
-
vertex: {
|
|
3641
|
-
header: (
|
|
3642
|
-
/* wgsl */
|
|
3643
|
-
`
|
|
3644
|
-
struct LocalUniforms {
|
|
3645
|
-
uColor:vec4<f32>,
|
|
3646
|
-
uTransformMatrix:mat3x3<f32>,
|
|
3647
|
-
uDistance: f32,
|
|
3648
|
-
uRound:f32,
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
|
-
@group(2) @binding(0) var<uniform> localUniforms : LocalUniforms;
|
|
3652
|
-
`
|
|
3653
|
-
),
|
|
3654
|
-
main: (
|
|
3655
|
-
/* wgsl */
|
|
3656
|
-
`
|
|
3657
|
-
vColor *= localUniforms.uColor;
|
|
3658
|
-
modelMatrix *= localUniforms.uTransformMatrix;
|
|
3659
|
-
`
|
|
3660
|
-
),
|
|
3661
|
-
end: (
|
|
3662
|
-
/* wgsl */
|
|
3663
|
-
`
|
|
3664
|
-
if(localUniforms.uRound == 1)
|
|
3665
|
-
{
|
|
3666
|
-
vPosition = vec4(roundPixels(vPosition.xy, globalUniforms.uResolution), vPosition.zw);
|
|
3667
|
-
}
|
|
3668
|
-
`
|
|
3669
|
-
)
|
|
3670
|
-
},
|
|
3671
|
-
fragment: {
|
|
3672
|
-
header: (
|
|
3673
|
-
/* wgsl */
|
|
3674
|
-
`
|
|
3675
|
-
struct LocalUniforms {
|
|
3676
|
-
uColor:vec4<f32>,
|
|
3677
|
-
uTransformMatrix:mat3x3<f32>,
|
|
3678
|
-
uDistance: f32
|
|
3679
|
-
}
|
|
3680
|
-
|
|
3681
|
-
@group(2) @binding(0) var<uniform> localUniforms : LocalUniforms;
|
|
3682
|
-
`
|
|
3683
|
-
),
|
|
3684
|
-
main: (
|
|
3685
|
-
/* wgsl */
|
|
3686
|
-
`
|
|
3687
|
-
outColor = vec4<f32>(calculateMSDFAlpha(outColor, localUniforms.uColor, localUniforms.uDistance));
|
|
3688
|
-
`
|
|
3689
|
-
)
|
|
3690
|
-
}
|
|
3691
|
-
};
|
|
3692
|
-
const localUniformMSDFBitGl = {
|
|
3693
|
-
name: "local-uniform-msdf-bit",
|
|
3694
|
-
vertex: {
|
|
3695
|
-
header: (
|
|
3696
|
-
/* glsl */
|
|
3697
|
-
`
|
|
3698
|
-
uniform mat3 uTransformMatrix;
|
|
3699
|
-
uniform vec4 uColor;
|
|
3700
|
-
uniform float uRound;
|
|
3701
|
-
`
|
|
3702
|
-
),
|
|
3703
|
-
main: (
|
|
3704
|
-
/* glsl */
|
|
3705
|
-
`
|
|
3706
|
-
vColor *= uColor;
|
|
3707
|
-
modelMatrix *= uTransformMatrix;
|
|
3708
|
-
`
|
|
3709
|
-
),
|
|
3710
|
-
end: (
|
|
3711
|
-
/* glsl */
|
|
3712
|
-
`
|
|
3713
|
-
if(uRound == 1.)
|
|
3714
|
-
{
|
|
3715
|
-
gl_Position.xy = roundPixels(gl_Position.xy, uResolution);
|
|
3716
|
-
}
|
|
3717
|
-
`
|
|
3718
|
-
)
|
|
3719
|
-
},
|
|
3720
|
-
fragment: {
|
|
3721
|
-
header: (
|
|
3722
|
-
/* glsl */
|
|
3723
|
-
`
|
|
3724
|
-
uniform float uDistance;
|
|
3725
|
-
`
|
|
3726
|
-
),
|
|
3727
|
-
main: (
|
|
3728
|
-
/* glsl */
|
|
3729
|
-
`
|
|
3730
|
-
outColor = vec4(calculateMSDFAlpha(outColor, vColor, uDistance));
|
|
3731
|
-
`
|
|
3732
|
-
)
|
|
3733
|
-
}
|
|
3734
|
-
};
|
|
3735
|
-
|
|
3736
|
-
const mSDFBit = {
|
|
3737
|
-
name: "msdf-bit",
|
|
3738
|
-
fragment: {
|
|
3739
|
-
header: (
|
|
3740
|
-
/* wgsl */
|
|
3741
|
-
`
|
|
3742
|
-
fn calculateMSDFAlpha(msdfColor:vec4<f32>, shapeColor:vec4<f32>, distance:f32) -> f32 {
|
|
3743
|
-
|
|
3744
|
-
// MSDF
|
|
3745
|
-
var median = msdfColor.r + msdfColor.g + msdfColor.b -
|
|
3746
|
-
min(msdfColor.r, min(msdfColor.g, msdfColor.b)) -
|
|
3747
|
-
max(msdfColor.r, max(msdfColor.g, msdfColor.b));
|
|
3748
|
-
|
|
3749
|
-
// SDF
|
|
3750
|
-
median = min(median, msdfColor.a);
|
|
3751
|
-
|
|
3752
|
-
var screenPxDistance = distance * (median - 0.5);
|
|
3753
|
-
var alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
|
3754
|
-
if (median < 0.01) {
|
|
3755
|
-
alpha = 0.0;
|
|
3756
|
-
} else if (median > 0.99) {
|
|
3757
|
-
alpha = 1.0;
|
|
3758
|
-
}
|
|
3759
|
-
|
|
3760
|
-
// Gamma correction for coverage-like alpha
|
|
3761
|
-
var luma: f32 = dot(shapeColor.rgb, vec3<f32>(0.299, 0.587, 0.114));
|
|
3762
|
-
var gamma: f32 = mix(1.0, 1.0 / 2.2, luma);
|
|
3763
|
-
var coverage: f32 = pow(shapeColor.a * alpha, gamma);
|
|
3764
|
-
|
|
3765
|
-
return coverage;
|
|
3766
|
-
|
|
3767
|
-
}
|
|
3768
|
-
`
|
|
3769
|
-
)
|
|
3770
|
-
}
|
|
3771
|
-
};
|
|
3772
|
-
const mSDFBitGl = {
|
|
3773
|
-
name: "msdf-bit",
|
|
3774
|
-
fragment: {
|
|
3775
|
-
header: (
|
|
3776
|
-
/* glsl */
|
|
3777
|
-
`
|
|
3778
|
-
float calculateMSDFAlpha(vec4 msdfColor, vec4 shapeColor, float distance) {
|
|
3779
|
-
|
|
3780
|
-
// MSDF
|
|
3781
|
-
float median = msdfColor.r + msdfColor.g + msdfColor.b -
|
|
3782
|
-
min(msdfColor.r, min(msdfColor.g, msdfColor.b)) -
|
|
3783
|
-
max(msdfColor.r, max(msdfColor.g, msdfColor.b));
|
|
3784
|
-
|
|
3785
|
-
// SDF
|
|
3786
|
-
median = min(median, msdfColor.a);
|
|
3787
|
-
|
|
3788
|
-
float screenPxDistance = distance * (median - 0.5);
|
|
3789
|
-
float alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0);
|
|
3790
|
-
|
|
3791
|
-
if (median < 0.01) {
|
|
3792
|
-
alpha = 0.0;
|
|
3793
|
-
} else if (median > 0.99) {
|
|
3794
|
-
alpha = 1.0;
|
|
3795
|
-
}
|
|
3796
|
-
|
|
3797
|
-
// Gamma correction for coverage-like alpha
|
|
3798
|
-
float luma = dot(shapeColor.rgb, vec3(0.299, 0.587, 0.114));
|
|
3799
|
-
float gamma = mix(1.0, 1.0 / 2.2, luma);
|
|
3800
|
-
float coverage = pow(shapeColor.a * alpha, gamma);
|
|
3801
|
-
|
|
3802
|
-
return coverage;
|
|
3803
|
-
}
|
|
3804
|
-
`
|
|
3805
|
-
)
|
|
3806
|
-
}
|
|
3807
|
-
};
|
|
3808
|
-
|
|
3809
|
-
let gpuProgram;
|
|
3810
|
-
let glProgram;
|
|
3811
|
-
class SdfShader extends Shader {
|
|
3812
|
-
constructor(maxTextures) {
|
|
3813
|
-
const uniforms = new UniformGroup({
|
|
3814
|
-
uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4<f32>" },
|
|
3815
|
-
uTransformMatrix: { value: new Matrix(), type: "mat3x3<f32>" },
|
|
3816
|
-
uDistance: { value: 4, type: "f32" },
|
|
3817
|
-
uRound: { value: 0, type: "f32" }
|
|
3818
|
-
});
|
|
3819
|
-
gpuProgram ?? (gpuProgram = compileHighShaderGpuProgram({
|
|
3820
|
-
name: "sdf-shader",
|
|
3821
|
-
bits: [
|
|
3822
|
-
colorBit,
|
|
3823
|
-
generateTextureBatchBit(maxTextures),
|
|
3824
|
-
localUniformMSDFBit,
|
|
3825
|
-
mSDFBit,
|
|
3826
|
-
roundPixelsBit
|
|
3827
|
-
]
|
|
3828
|
-
}));
|
|
3829
|
-
glProgram ?? (glProgram = compileHighShaderGlProgram({
|
|
3830
|
-
name: "sdf-shader",
|
|
3831
|
-
bits: [
|
|
3832
|
-
colorBitGl,
|
|
3833
|
-
generateTextureBatchBitGl(maxTextures),
|
|
3834
|
-
localUniformMSDFBitGl,
|
|
3835
|
-
mSDFBitGl,
|
|
3836
|
-
roundPixelsBitGl
|
|
3837
|
-
]
|
|
3838
|
-
}));
|
|
3839
|
-
super({
|
|
3840
|
-
glProgram,
|
|
3841
|
-
gpuProgram,
|
|
3842
|
-
resources: {
|
|
3843
|
-
localUniforms: uniforms,
|
|
3844
|
-
batchSamplers: getBatchSamplersUniformGroup(maxTextures)
|
|
3845
|
-
}
|
|
3846
|
-
});
|
|
3847
|
-
}
|
|
3848
|
-
}
|
|
3849
|
-
|
|
3850
|
-
class BitmapTextGraphics extends Graphics {
|
|
3851
|
-
destroy() {
|
|
3852
|
-
if (this.context.customShader) {
|
|
3853
|
-
this.context.customShader.destroy();
|
|
3854
|
-
}
|
|
3855
|
-
super.destroy();
|
|
3856
|
-
}
|
|
3857
|
-
}
|
|
3858
|
-
class BitmapTextPipe {
|
|
3859
|
-
constructor(renderer) {
|
|
3860
|
-
this._renderer = renderer;
|
|
3861
|
-
this._renderer.renderableGC.addManagedHash(this, "_gpuBitmapText");
|
|
3862
|
-
}
|
|
3863
|
-
validateRenderable(bitmapText) {
|
|
3864
|
-
const graphicsRenderable = this._getGpuBitmapText(bitmapText);
|
|
3865
|
-
if (bitmapText._didTextUpdate) {
|
|
3866
|
-
bitmapText._didTextUpdate = false;
|
|
3867
|
-
this._updateContext(bitmapText, graphicsRenderable);
|
|
3868
|
-
}
|
|
3869
|
-
return this._renderer.renderPipes.graphics.validateRenderable(graphicsRenderable);
|
|
3870
|
-
}
|
|
3871
|
-
addRenderable(bitmapText, instructionSet) {
|
|
3872
|
-
const graphicsRenderable = this._getGpuBitmapText(bitmapText);
|
|
3873
|
-
syncWithProxy(bitmapText, graphicsRenderable);
|
|
3874
|
-
if (bitmapText._didTextUpdate) {
|
|
3875
|
-
bitmapText._didTextUpdate = false;
|
|
3876
|
-
this._updateContext(bitmapText, graphicsRenderable);
|
|
3877
|
-
}
|
|
3878
|
-
this._renderer.renderPipes.graphics.addRenderable(graphicsRenderable, instructionSet);
|
|
3879
|
-
if (graphicsRenderable.context.customShader) {
|
|
3880
|
-
this._updateDistanceField(bitmapText);
|
|
3881
|
-
}
|
|
3882
|
-
}
|
|
3883
|
-
updateRenderable(bitmapText) {
|
|
3884
|
-
const graphicsRenderable = this._getGpuBitmapText(bitmapText);
|
|
3885
|
-
syncWithProxy(bitmapText, graphicsRenderable);
|
|
3886
|
-
this._renderer.renderPipes.graphics.updateRenderable(graphicsRenderable);
|
|
3887
|
-
if (graphicsRenderable.context.customShader) {
|
|
3888
|
-
this._updateDistanceField(bitmapText);
|
|
3889
|
-
}
|
|
3890
|
-
}
|
|
3891
|
-
_updateContext(bitmapText, proxyGraphics) {
|
|
3892
|
-
const { context } = proxyGraphics;
|
|
3893
|
-
const bitmapFont = BitmapFontManager.getFont(bitmapText.text, bitmapText._style);
|
|
3894
|
-
context.clear();
|
|
3895
|
-
if (bitmapFont.distanceField.type !== "none") {
|
|
3896
|
-
if (!context.customShader) {
|
|
3897
|
-
context.customShader = new SdfShader(this._renderer.limits.maxBatchableTextures);
|
|
3898
|
-
}
|
|
3899
|
-
}
|
|
3900
|
-
const chars = CanvasTextMetrics.graphemeSegmenter(bitmapText.text);
|
|
3901
|
-
const style = bitmapText._style;
|
|
3902
|
-
let currentY = bitmapFont.baseLineOffset;
|
|
3903
|
-
const bitmapTextLayout = getBitmapTextLayout(chars, style, bitmapFont, true);
|
|
3904
|
-
const padding = style.padding;
|
|
3905
|
-
const scale = bitmapTextLayout.scale;
|
|
3906
|
-
let tx = bitmapTextLayout.width;
|
|
3907
|
-
let ty = bitmapTextLayout.height + bitmapTextLayout.offsetY;
|
|
3908
|
-
if (style._stroke) {
|
|
3909
|
-
tx += style._stroke.width / scale;
|
|
3910
|
-
ty += style._stroke.width / scale;
|
|
3911
|
-
}
|
|
3912
|
-
context.translate(-bitmapText._anchor._x * tx - padding, -bitmapText._anchor._y * ty - padding).scale(scale, scale);
|
|
3913
|
-
const tint = bitmapFont.applyFillAsTint ? style._fill.color : 16777215;
|
|
3914
|
-
for (let i = 0; i < bitmapTextLayout.lines.length; i++) {
|
|
3915
|
-
const line = bitmapTextLayout.lines[i];
|
|
3916
|
-
for (let j = 0; j < line.charPositions.length; j++) {
|
|
3917
|
-
const char = line.chars[j];
|
|
3918
|
-
const charData = bitmapFont.chars[char];
|
|
3919
|
-
if (charData?.texture) {
|
|
3920
|
-
context.texture(
|
|
3921
|
-
charData.texture,
|
|
3922
|
-
tint ? tint : "black",
|
|
3923
|
-
Math.round(line.charPositions[j] + charData.xOffset),
|
|
3924
|
-
Math.round(currentY + charData.yOffset)
|
|
3925
|
-
);
|
|
3926
|
-
}
|
|
3927
|
-
}
|
|
3928
|
-
currentY += bitmapFont.lineHeight;
|
|
3929
|
-
}
|
|
3930
|
-
}
|
|
3931
|
-
_getGpuBitmapText(bitmapText) {
|
|
3932
|
-
return bitmapText._gpuData[this._renderer.uid] || this.initGpuText(bitmapText);
|
|
3933
|
-
}
|
|
3934
|
-
initGpuText(bitmapText) {
|
|
3935
|
-
const proxyRenderable = new BitmapTextGraphics();
|
|
3936
|
-
bitmapText._gpuData[this._renderer.uid] = proxyRenderable;
|
|
3937
|
-
this._updateContext(bitmapText, proxyRenderable);
|
|
3938
|
-
return proxyRenderable;
|
|
3939
|
-
}
|
|
3940
|
-
_updateDistanceField(bitmapText) {
|
|
3941
|
-
const context = this._getGpuBitmapText(bitmapText).context;
|
|
3942
|
-
const fontFamily = bitmapText._style.fontFamily;
|
|
3943
|
-
const dynamicFont = Cache.get(`${fontFamily}-bitmap`);
|
|
3944
|
-
const { a, b, c, d } = bitmapText.groupTransform;
|
|
3945
|
-
const dx = Math.sqrt(a * a + b * b);
|
|
3946
|
-
const dy = Math.sqrt(c * c + d * d);
|
|
3947
|
-
const worldScale = (Math.abs(dx) + Math.abs(dy)) / 2;
|
|
3948
|
-
const fontScale = dynamicFont.baseRenderedFontSize / bitmapText._style.fontSize;
|
|
3949
|
-
const distance = worldScale * dynamicFont.distanceField.range * (1 / fontScale);
|
|
3950
|
-
context.customShader.resources.localUniforms.uniforms.uDistance = distance;
|
|
3951
|
-
}
|
|
3952
|
-
destroy() {
|
|
3953
|
-
this._renderer = null;
|
|
3954
|
-
}
|
|
3955
|
-
}
|
|
3956
|
-
/** @ignore */
|
|
3957
|
-
BitmapTextPipe.extension = {
|
|
3958
|
-
type: [
|
|
3959
|
-
ExtensionType.WebGLPipes,
|
|
3960
|
-
ExtensionType.WebGPUPipes,
|
|
3961
|
-
ExtensionType.CanvasPipes
|
|
3962
|
-
],
|
|
3963
|
-
name: "bitmapText"
|
|
3964
|
-
};
|
|
3965
|
-
function syncWithProxy(container, proxy) {
|
|
3966
|
-
proxy.groupTransform = container.groupTransform;
|
|
3967
|
-
proxy.groupColorAlpha = container.groupColorAlpha;
|
|
3968
|
-
proxy.groupColor = container.groupColor;
|
|
3969
|
-
proxy.groupBlendMode = container.groupBlendMode;
|
|
3970
|
-
proxy.globalDisplayStatus = container.globalDisplayStatus;
|
|
3971
|
-
proxy.groupTransform = container.groupTransform;
|
|
3972
|
-
proxy.localDisplayStatus = container.localDisplayStatus;
|
|
3973
|
-
proxy.groupAlpha = container.groupAlpha;
|
|
3974
|
-
proxy._roundPixels = container._roundPixels;
|
|
3975
|
-
}
|
|
3976
|
-
|
|
3977
|
-
class BatchableHTMLText extends BatchableSprite {
|
|
3978
|
-
/**
|
|
3979
|
-
* Creates an instance of BatchableHTMLText.
|
|
3980
|
-
* @param renderer - The renderer instance to be used.
|
|
3981
|
-
*/
|
|
3982
|
-
constructor(renderer) {
|
|
3983
|
-
super();
|
|
3984
|
-
this.generatingTexture = false;
|
|
3985
|
-
this._renderer = renderer;
|
|
3986
|
-
renderer.runners.resolutionChange.add(this);
|
|
3987
|
-
}
|
|
3988
|
-
/** Handles resolution changes for the HTML text. If the text has auto resolution enabled, it triggers a view update. */
|
|
3989
|
-
resolutionChange() {
|
|
3990
|
-
const text = this.renderable;
|
|
3991
|
-
if (text._autoResolution) {
|
|
3992
|
-
text.onViewUpdate();
|
|
3993
|
-
}
|
|
3994
|
-
}
|
|
3995
|
-
/** Destroys the BatchableHTMLText instance. Returns the texture promise to the renderer and cleans up references. */
|
|
3996
|
-
destroy() {
|
|
3997
|
-
this._renderer.htmlText.returnTexturePromise(this.texturePromise);
|
|
3998
|
-
this.texturePromise = null;
|
|
3999
|
-
this._renderer = null;
|
|
4000
|
-
}
|
|
4001
|
-
}
|
|
4002
|
-
|
|
4003
|
-
function updateTextBounds(batchableSprite, text) {
|
|
4004
|
-
const { texture, bounds } = batchableSprite;
|
|
4005
|
-
const padding = text._style._getFinalPadding();
|
|
4006
|
-
updateQuadBounds(bounds, text._anchor, texture);
|
|
4007
|
-
const paddingOffset = text._anchor._x * padding * 2;
|
|
4008
|
-
const paddingOffsetY = text._anchor._y * padding * 2;
|
|
4009
|
-
bounds.minX -= padding - paddingOffset;
|
|
4010
|
-
bounds.minY -= padding - paddingOffsetY;
|
|
4011
|
-
bounds.maxX -= padding - paddingOffset;
|
|
4012
|
-
bounds.maxY -= padding - paddingOffsetY;
|
|
4013
|
-
}
|
|
4014
|
-
|
|
4015
|
-
class HTMLTextPipe {
|
|
4016
|
-
constructor(renderer) {
|
|
4017
|
-
this._renderer = renderer;
|
|
4018
|
-
}
|
|
4019
|
-
validateRenderable(htmlText) {
|
|
4020
|
-
return htmlText._didTextUpdate;
|
|
4021
|
-
}
|
|
4022
|
-
addRenderable(htmlText, instructionSet) {
|
|
4023
|
-
const batchableHTMLText = this._getGpuText(htmlText);
|
|
4024
|
-
if (htmlText._didTextUpdate) {
|
|
4025
|
-
this._updateGpuText(htmlText).catch((e) => {
|
|
4026
|
-
console.error(e);
|
|
4027
|
-
});
|
|
4028
|
-
htmlText._didTextUpdate = false;
|
|
4029
|
-
updateTextBounds(batchableHTMLText, htmlText);
|
|
4030
|
-
}
|
|
4031
|
-
this._renderer.renderPipes.batch.addToBatch(batchableHTMLText, instructionSet);
|
|
4032
|
-
}
|
|
4033
|
-
updateRenderable(htmlText) {
|
|
4034
|
-
const batchableHTMLText = this._getGpuText(htmlText);
|
|
4035
|
-
batchableHTMLText._batcher.updateElement(batchableHTMLText);
|
|
4036
|
-
}
|
|
4037
|
-
async _updateGpuText(htmlText) {
|
|
4038
|
-
htmlText._didTextUpdate = false;
|
|
4039
|
-
const batchableHTMLText = this._getGpuText(htmlText);
|
|
4040
|
-
if (batchableHTMLText.generatingTexture)
|
|
4041
|
-
return;
|
|
4042
|
-
if (batchableHTMLText.texturePromise) {
|
|
4043
|
-
this._renderer.htmlText.returnTexturePromise(batchableHTMLText.texturePromise);
|
|
4044
|
-
batchableHTMLText.texturePromise = null;
|
|
4045
|
-
}
|
|
4046
|
-
batchableHTMLText.generatingTexture = true;
|
|
4047
|
-
htmlText._resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution;
|
|
4048
|
-
const texturePromise = this._renderer.htmlText.getTexturePromise(htmlText);
|
|
4049
|
-
batchableHTMLText.texturePromise = texturePromise;
|
|
4050
|
-
batchableHTMLText.texture = await texturePromise;
|
|
4051
|
-
const renderGroup = htmlText.renderGroup || htmlText.parentRenderGroup;
|
|
4052
|
-
if (renderGroup) {
|
|
4053
|
-
renderGroup.structureDidChange = true;
|
|
4054
|
-
}
|
|
4055
|
-
batchableHTMLText.generatingTexture = false;
|
|
4056
|
-
updateTextBounds(batchableHTMLText, htmlText);
|
|
4057
|
-
}
|
|
4058
|
-
_getGpuText(htmlText) {
|
|
4059
|
-
return htmlText._gpuData[this._renderer.uid] || this.initGpuText(htmlText);
|
|
4060
|
-
}
|
|
4061
|
-
initGpuText(htmlText) {
|
|
4062
|
-
const batchableHTMLText = new BatchableHTMLText(this._renderer);
|
|
4063
|
-
batchableHTMLText.renderable = htmlText;
|
|
4064
|
-
batchableHTMLText.transform = htmlText.groupTransform;
|
|
4065
|
-
batchableHTMLText.texture = Texture.EMPTY;
|
|
4066
|
-
batchableHTMLText.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
|
|
4067
|
-
batchableHTMLText.roundPixels = this._renderer._roundPixels | htmlText._roundPixels;
|
|
4068
|
-
htmlText._resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution;
|
|
4069
|
-
htmlText._gpuData[this._renderer.uid] = batchableHTMLText;
|
|
4070
|
-
return batchableHTMLText;
|
|
4071
|
-
}
|
|
4072
|
-
destroy() {
|
|
4073
|
-
this._renderer = null;
|
|
4074
|
-
}
|
|
4075
|
-
}
|
|
4076
|
-
/** @ignore */
|
|
4077
|
-
HTMLTextPipe.extension = {
|
|
4078
|
-
type: [
|
|
4079
|
-
ExtensionType.WebGLPipes,
|
|
4080
|
-
ExtensionType.WebGPUPipes,
|
|
4081
|
-
ExtensionType.CanvasPipes
|
|
4082
|
-
],
|
|
4083
|
-
name: "htmlText"
|
|
4084
|
-
};
|
|
4085
|
-
|
|
4086
|
-
function isSafari() {
|
|
4087
|
-
const { userAgent } = DOMAdapter.get().getNavigator();
|
|
4088
|
-
return /^((?!chrome|android).)*safari/i.test(userAgent);
|
|
4089
|
-
}
|
|
4090
|
-
|
|
4091
|
-
const tempBounds = new Bounds();
|
|
4092
|
-
function getPo2TextureFromSource(image, width, height, resolution) {
|
|
4093
|
-
const bounds = tempBounds;
|
|
4094
|
-
bounds.minX = 0;
|
|
4095
|
-
bounds.minY = 0;
|
|
4096
|
-
bounds.maxX = image.width / resolution | 0;
|
|
4097
|
-
bounds.maxY = image.height / resolution | 0;
|
|
4098
|
-
const texture = TexturePool.getOptimalTexture(
|
|
4099
|
-
bounds.width,
|
|
4100
|
-
bounds.height,
|
|
4101
|
-
resolution,
|
|
4102
|
-
false
|
|
4103
|
-
);
|
|
4104
|
-
texture.source.uploadMethodId = "image";
|
|
4105
|
-
texture.source.resource = image;
|
|
4106
|
-
texture.source.alphaMode = "premultiply-alpha-on-upload";
|
|
4107
|
-
texture.frame.width = width / resolution;
|
|
4108
|
-
texture.frame.height = height / resolution;
|
|
4109
|
-
texture.source.emit("update", texture.source);
|
|
4110
|
-
texture.updateUvs();
|
|
4111
|
-
return texture;
|
|
4112
|
-
}
|
|
4113
|
-
|
|
4114
|
-
function extractFontFamilies(text, style) {
|
|
4115
|
-
const fontFamily = style.fontFamily;
|
|
4116
|
-
const fontFamilies = [];
|
|
4117
|
-
const dedupe = {};
|
|
4118
|
-
const regex = /font-family:([^;"\s]+)/g;
|
|
4119
|
-
const matches = text.match(regex);
|
|
4120
|
-
function addFontFamily(fontFamily2) {
|
|
4121
|
-
if (!dedupe[fontFamily2]) {
|
|
4122
|
-
fontFamilies.push(fontFamily2);
|
|
4123
|
-
dedupe[fontFamily2] = true;
|
|
4124
|
-
}
|
|
4125
|
-
}
|
|
4126
|
-
if (Array.isArray(fontFamily)) {
|
|
4127
|
-
for (let i = 0; i < fontFamily.length; i++) {
|
|
4128
|
-
addFontFamily(fontFamily[i]);
|
|
4129
|
-
}
|
|
4130
|
-
} else {
|
|
4131
|
-
addFontFamily(fontFamily);
|
|
4132
|
-
}
|
|
4133
|
-
if (matches) {
|
|
4134
|
-
matches.forEach((match) => {
|
|
4135
|
-
const fontFamily2 = match.split(":")[1].trim();
|
|
4136
|
-
addFontFamily(fontFamily2);
|
|
4137
|
-
});
|
|
4138
|
-
}
|
|
4139
|
-
for (const i in style.tagStyles) {
|
|
4140
|
-
const fontFamily2 = style.tagStyles[i].fontFamily;
|
|
4141
|
-
addFontFamily(fontFamily2);
|
|
4142
|
-
}
|
|
4143
|
-
return fontFamilies;
|
|
4144
|
-
}
|
|
4145
|
-
|
|
4146
|
-
async function loadFontAsBase64(url) {
|
|
4147
|
-
const response = await DOMAdapter.get().fetch(url);
|
|
4148
|
-
const blob = await response.blob();
|
|
4149
|
-
const reader = new FileReader();
|
|
4150
|
-
const dataSrc = await new Promise((resolve, reject) => {
|
|
4151
|
-
reader.onloadend = () => resolve(reader.result);
|
|
4152
|
-
reader.onerror = reject;
|
|
4153
|
-
reader.readAsDataURL(blob);
|
|
4154
|
-
});
|
|
4155
|
-
return dataSrc;
|
|
4156
|
-
}
|
|
4157
|
-
|
|
4158
|
-
async function loadFontCSS(style, url) {
|
|
4159
|
-
const dataSrc = await loadFontAsBase64(url);
|
|
4160
|
-
return `@font-face {
|
|
4161
|
-
font-family: "${style.fontFamily}";
|
|
4162
|
-
src: url('${dataSrc}');
|
|
4163
|
-
font-weight: ${style.fontWeight};
|
|
4164
|
-
font-style: ${style.fontStyle};
|
|
4165
|
-
}`;
|
|
4166
|
-
}
|
|
4167
|
-
|
|
4168
|
-
const FontStylePromiseCache = /* @__PURE__ */ new Map();
|
|
4169
|
-
async function getFontCss(fontFamilies, style, defaultOptions) {
|
|
4170
|
-
const fontPromises = fontFamilies.filter((fontFamily) => Cache.has(`${fontFamily}-and-url`)).map((fontFamily, i) => {
|
|
4171
|
-
if (!FontStylePromiseCache.has(fontFamily)) {
|
|
4172
|
-
const { url } = Cache.get(`${fontFamily}-and-url`);
|
|
4173
|
-
if (i === 0) {
|
|
4174
|
-
FontStylePromiseCache.set(fontFamily, loadFontCSS({
|
|
4175
|
-
fontWeight: style.fontWeight,
|
|
4176
|
-
fontStyle: style.fontStyle,
|
|
4177
|
-
fontFamily
|
|
4178
|
-
}, url));
|
|
4179
|
-
} else {
|
|
4180
|
-
FontStylePromiseCache.set(fontFamily, loadFontCSS({
|
|
4181
|
-
fontWeight: defaultOptions.fontWeight,
|
|
4182
|
-
fontStyle: defaultOptions.fontStyle,
|
|
4183
|
-
fontFamily
|
|
4184
|
-
}, url));
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
return FontStylePromiseCache.get(fontFamily);
|
|
4188
|
-
});
|
|
4189
|
-
return (await Promise.all(fontPromises)).join("\n");
|
|
4190
|
-
}
|
|
4191
|
-
|
|
4192
|
-
function getSVGUrl(text, style, resolution, fontCSS, htmlTextData) {
|
|
4193
|
-
const { domElement, styleElement, svgRoot } = htmlTextData;
|
|
4194
|
-
domElement.innerHTML = `<style>${style.cssStyle}</style><div style='padding:0;'>${text}</div>`;
|
|
4195
|
-
domElement.setAttribute("style", `transform: scale(${resolution});transform-origin: top left; display: inline-block`);
|
|
4196
|
-
styleElement.textContent = fontCSS;
|
|
4197
|
-
const { width, height } = htmlTextData.image;
|
|
4198
|
-
svgRoot.setAttribute("width", width.toString());
|
|
4199
|
-
svgRoot.setAttribute("height", height.toString());
|
|
4200
|
-
return new XMLSerializer().serializeToString(svgRoot);
|
|
4201
|
-
}
|
|
4202
|
-
|
|
4203
|
-
function getTemporaryCanvasFromImage(image, resolution) {
|
|
4204
|
-
const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(
|
|
4205
|
-
image.width,
|
|
4206
|
-
image.height,
|
|
4207
|
-
resolution
|
|
4208
|
-
);
|
|
4209
|
-
const { context } = canvasAndContext;
|
|
4210
|
-
context.clearRect(0, 0, image.width, image.height);
|
|
4211
|
-
context.drawImage(image, 0, 0);
|
|
4212
|
-
return canvasAndContext;
|
|
4213
|
-
}
|
|
4214
|
-
|
|
4215
|
-
function loadSVGImage(image, url, delay) {
|
|
4216
|
-
return new Promise(async (resolve) => {
|
|
4217
|
-
if (delay) {
|
|
4218
|
-
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
4219
|
-
}
|
|
4220
|
-
image.onload = () => {
|
|
4221
|
-
resolve();
|
|
4222
|
-
};
|
|
4223
|
-
image.src = `data:image/svg+xml;charset=utf8,${encodeURIComponent(url)}`;
|
|
4224
|
-
image.crossOrigin = "anonymous";
|
|
4225
|
-
});
|
|
4226
|
-
}
|
|
4227
|
-
|
|
4228
|
-
class HTMLTextSystem {
|
|
4229
|
-
constructor(renderer) {
|
|
4230
|
-
this._renderer = renderer;
|
|
4231
|
-
this._createCanvas = renderer.type === RendererType.WEBGPU;
|
|
4232
|
-
}
|
|
4233
|
-
/**
|
|
4234
|
-
* @param options
|
|
4235
|
-
* @deprecated Use getTexturePromise instead
|
|
4236
|
-
*/
|
|
4237
|
-
getTexture(options) {
|
|
4238
|
-
return this.getTexturePromise(options);
|
|
4239
|
-
}
|
|
4240
|
-
getTexturePromise(options) {
|
|
4241
|
-
return this._buildTexturePromise(options);
|
|
4242
|
-
}
|
|
4243
|
-
async _buildTexturePromise(options) {
|
|
4244
|
-
const { text, style, resolution, textureStyle } = options;
|
|
4245
|
-
const htmlTextData = BigPool.get(HTMLTextRenderData);
|
|
4246
|
-
const fontFamilies = extractFontFamilies(text, style);
|
|
4247
|
-
const fontCSS = await getFontCss(
|
|
4248
|
-
fontFamilies,
|
|
4249
|
-
style,
|
|
4250
|
-
HTMLTextStyle.defaultTextStyle
|
|
4251
|
-
);
|
|
4252
|
-
const measured = measureHtmlText(text, style, fontCSS, htmlTextData);
|
|
4253
|
-
const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + style.padding * 2) * resolution);
|
|
4254
|
-
const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + style.padding * 2) * resolution);
|
|
4255
|
-
const image = htmlTextData.image;
|
|
4256
|
-
const uvSafeOffset = 2;
|
|
4257
|
-
image.width = (width | 0) + uvSafeOffset;
|
|
4258
|
-
image.height = (height | 0) + uvSafeOffset;
|
|
4259
|
-
const svgURL = getSVGUrl(text, style, resolution, fontCSS, htmlTextData);
|
|
4260
|
-
await loadSVGImage(image, svgURL, isSafari() && fontFamilies.length > 0);
|
|
4261
|
-
const resource = image;
|
|
4262
|
-
let canvasAndContext;
|
|
4263
|
-
if (this._createCanvas) {
|
|
4264
|
-
canvasAndContext = getTemporaryCanvasFromImage(image, resolution);
|
|
4265
|
-
}
|
|
4266
|
-
const texture = getPo2TextureFromSource(
|
|
4267
|
-
canvasAndContext ? canvasAndContext.canvas : resource,
|
|
4268
|
-
image.width - uvSafeOffset,
|
|
4269
|
-
image.height - uvSafeOffset,
|
|
4270
|
-
resolution
|
|
4271
|
-
);
|
|
4272
|
-
if (textureStyle)
|
|
4273
|
-
texture.source.style = textureStyle;
|
|
4274
|
-
if (this._createCanvas) {
|
|
4275
|
-
this._renderer.texture.initSource(texture.source);
|
|
4276
|
-
CanvasPool.returnCanvasAndContext(canvasAndContext);
|
|
4277
|
-
}
|
|
4278
|
-
BigPool.return(htmlTextData);
|
|
4279
|
-
return texture;
|
|
4280
|
-
}
|
|
4281
|
-
returnTexturePromise(texturePromise) {
|
|
4282
|
-
texturePromise.then((texture) => {
|
|
4283
|
-
this._cleanUp(texture);
|
|
4284
|
-
}).catch(() => {
|
|
4285
|
-
warn("HTMLTextSystem: Failed to clean texture");
|
|
4286
|
-
});
|
|
4287
|
-
}
|
|
4288
|
-
_cleanUp(texture) {
|
|
4289
|
-
TexturePool.returnTexture(texture, true);
|
|
4290
|
-
texture.source.resource = null;
|
|
4291
|
-
texture.source.uploadMethodId = "unknown";
|
|
4292
|
-
}
|
|
4293
|
-
destroy() {
|
|
4294
|
-
this._renderer = null;
|
|
4295
|
-
}
|
|
4296
|
-
}
|
|
4297
|
-
/** @ignore */
|
|
4298
|
-
HTMLTextSystem.extension = {
|
|
4299
|
-
type: [
|
|
4300
|
-
ExtensionType.WebGLSystem,
|
|
4301
|
-
ExtensionType.WebGPUSystem,
|
|
4302
|
-
ExtensionType.CanvasSystem
|
|
4303
|
-
],
|
|
4304
|
-
name: "htmlText"
|
|
4305
|
-
};
|
|
4306
|
-
|
|
4307
|
-
class BatchableText extends BatchableSprite {
|
|
4308
|
-
constructor(renderer) {
|
|
4309
|
-
super();
|
|
4310
|
-
this._renderer = renderer;
|
|
4311
|
-
renderer.runners.resolutionChange.add(this);
|
|
4312
|
-
}
|
|
4313
|
-
resolutionChange() {
|
|
4314
|
-
const text = this.renderable;
|
|
4315
|
-
if (text._autoResolution) {
|
|
4316
|
-
text.onViewUpdate();
|
|
4317
|
-
}
|
|
4318
|
-
}
|
|
4319
|
-
destroy() {
|
|
4320
|
-
this._renderer.canvasText.returnTexture(this.texture);
|
|
4321
|
-
this._renderer = null;
|
|
4322
|
-
}
|
|
4323
|
-
}
|
|
4324
|
-
|
|
4325
|
-
class CanvasTextPipe {
|
|
4326
|
-
constructor(renderer) {
|
|
4327
|
-
this._renderer = renderer;
|
|
4328
|
-
}
|
|
4329
|
-
validateRenderable(text) {
|
|
4330
|
-
return text._didTextUpdate;
|
|
4331
|
-
}
|
|
4332
|
-
addRenderable(text, instructionSet) {
|
|
4333
|
-
const batchableText = this._getGpuText(text);
|
|
4334
|
-
if (text._didTextUpdate) {
|
|
4335
|
-
this._updateGpuText(text);
|
|
4336
|
-
text._didTextUpdate = false;
|
|
4337
|
-
}
|
|
4338
|
-
this._renderer.renderPipes.batch.addToBatch(batchableText, instructionSet);
|
|
4339
|
-
}
|
|
4340
|
-
updateRenderable(text) {
|
|
4341
|
-
const batchableText = this._getGpuText(text);
|
|
4342
|
-
batchableText._batcher.updateElement(batchableText);
|
|
4343
|
-
}
|
|
4344
|
-
_updateGpuText(text) {
|
|
4345
|
-
const batchableText = this._getGpuText(text);
|
|
4346
|
-
if (batchableText.texture) {
|
|
4347
|
-
this._renderer.canvasText.returnTexture(batchableText.texture);
|
|
4348
|
-
}
|
|
4349
|
-
text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
|
|
4350
|
-
batchableText.texture = batchableText.texture = this._renderer.canvasText.getTexture(text);
|
|
4351
|
-
updateTextBounds(batchableText, text);
|
|
4352
|
-
}
|
|
4353
|
-
_getGpuText(text) {
|
|
4354
|
-
return text._gpuData[this._renderer.uid] || this.initGpuText(text);
|
|
4355
|
-
}
|
|
4356
|
-
initGpuText(text) {
|
|
4357
|
-
const batchableText = new BatchableText(this._renderer);
|
|
4358
|
-
batchableText.renderable = text;
|
|
4359
|
-
batchableText.transform = text.groupTransform;
|
|
4360
|
-
batchableText.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
|
|
4361
|
-
batchableText.roundPixels = this._renderer._roundPixels | text._roundPixels;
|
|
4362
|
-
text._gpuData[this._renderer.uid] = batchableText;
|
|
4363
|
-
return batchableText;
|
|
4364
|
-
}
|
|
4365
|
-
destroy() {
|
|
4366
|
-
this._renderer = null;
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
/** @ignore */
|
|
4370
|
-
CanvasTextPipe.extension = {
|
|
4371
|
-
type: [
|
|
4372
|
-
ExtensionType.WebGLPipes,
|
|
4373
|
-
ExtensionType.WebGPUPipes,
|
|
4374
|
-
ExtensionType.CanvasPipes
|
|
4375
|
-
],
|
|
4376
|
-
name: "text"
|
|
4377
|
-
};
|
|
4378
|
-
|
|
4379
|
-
class CanvasTextSystem {
|
|
4380
|
-
constructor(_renderer) {
|
|
4381
|
-
this._renderer = _renderer;
|
|
4382
|
-
}
|
|
4383
|
-
getTexture(options, _resolution, _style, _textKey) {
|
|
4384
|
-
if (typeof options === "string") {
|
|
4385
|
-
deprecation("8.0.0", "CanvasTextSystem.getTexture: Use object TextOptions instead of separate arguments");
|
|
4386
|
-
options = {
|
|
4387
|
-
text: options,
|
|
4388
|
-
style: _style,
|
|
4389
|
-
resolution: _resolution
|
|
4390
|
-
};
|
|
4391
|
-
}
|
|
4392
|
-
if (!(options.style instanceof TextStyle)) {
|
|
4393
|
-
options.style = new TextStyle(options.style);
|
|
4394
|
-
}
|
|
4395
|
-
if (!(options.textureStyle instanceof TextureStyle)) {
|
|
4396
|
-
options.textureStyle = new TextureStyle(options.textureStyle);
|
|
4397
|
-
}
|
|
4398
|
-
if (typeof options.text !== "string") {
|
|
4399
|
-
options.text = options.text.toString();
|
|
4400
|
-
}
|
|
4401
|
-
const { text, style, textureStyle } = options;
|
|
4402
|
-
const resolution = options.resolution ?? this._renderer.resolution;
|
|
4403
|
-
const { frame, canvasAndContext } = CanvasTextGenerator.getCanvasAndContext({
|
|
4404
|
-
text,
|
|
4405
|
-
style,
|
|
4406
|
-
resolution
|
|
4407
|
-
});
|
|
4408
|
-
const texture = getPo2TextureFromSource(canvasAndContext.canvas, frame.width, frame.height, resolution);
|
|
4409
|
-
if (textureStyle)
|
|
4410
|
-
texture.source.style = textureStyle;
|
|
4411
|
-
if (style.trim) {
|
|
4412
|
-
frame.pad(style.padding);
|
|
4413
|
-
texture.frame.copyFrom(frame);
|
|
4414
|
-
texture.updateUvs();
|
|
4415
|
-
}
|
|
4416
|
-
if (style.filters) {
|
|
4417
|
-
const filteredTexture = this._applyFilters(texture, style.filters);
|
|
4418
|
-
this.returnTexture(texture);
|
|
4419
|
-
CanvasTextGenerator.returnCanvasAndContext(canvasAndContext);
|
|
4420
|
-
return filteredTexture;
|
|
4421
|
-
}
|
|
4422
|
-
this._renderer.texture.initSource(texture._source);
|
|
4423
|
-
CanvasTextGenerator.returnCanvasAndContext(canvasAndContext);
|
|
4424
|
-
return texture;
|
|
4425
|
-
}
|
|
4426
|
-
/**
|
|
4427
|
-
* Returns a texture that was created wit the above `getTexture` function.
|
|
4428
|
-
* Handy if you are done with a texture and want to return it to the pool.
|
|
4429
|
-
* @param texture - The texture to be returned.
|
|
4430
|
-
*/
|
|
4431
|
-
returnTexture(texture) {
|
|
4432
|
-
const source = texture.source;
|
|
4433
|
-
source.resource = null;
|
|
4434
|
-
source.uploadMethodId = "unknown";
|
|
4435
|
-
source.alphaMode = "no-premultiply-alpha";
|
|
4436
|
-
TexturePool.returnTexture(texture, true);
|
|
4437
|
-
}
|
|
4438
|
-
/**
|
|
4439
|
-
* Renders text to its canvas, and updates its texture.
|
|
4440
|
-
* @deprecated since 8.10.0
|
|
4441
|
-
*/
|
|
4442
|
-
renderTextToCanvas() {
|
|
4443
|
-
deprecation(
|
|
4444
|
-
"8.10.0",
|
|
4445
|
-
"CanvasTextSystem.renderTextToCanvas: no longer supported, use CanvasTextSystem.getTexture instead"
|
|
4446
|
-
);
|
|
4447
|
-
}
|
|
4448
|
-
/**
|
|
4449
|
-
* Applies the specified filters to the given texture.
|
|
4450
|
-
*
|
|
4451
|
-
* This method takes a texture and a list of filters, applies the filters to the texture,
|
|
4452
|
-
* and returns the resulting texture. It also ensures that the alpha mode of the resulting
|
|
4453
|
-
* texture is set to 'premultiplied-alpha'.
|
|
4454
|
-
* @param {Texture} texture - The texture to which the filters will be applied.
|
|
4455
|
-
* @param {Filter[]} filters - The filters to apply to the texture.
|
|
4456
|
-
* @returns {Texture} The resulting texture after all filters have been applied.
|
|
4457
|
-
*/
|
|
4458
|
-
_applyFilters(texture, filters) {
|
|
4459
|
-
const currentRenderTarget = this._renderer.renderTarget.renderTarget;
|
|
4460
|
-
const resultTexture = this._renderer.filter.generateFilteredTexture({
|
|
4461
|
-
texture,
|
|
4462
|
-
filters
|
|
4463
|
-
});
|
|
4464
|
-
this._renderer.renderTarget.bind(currentRenderTarget, false);
|
|
4465
|
-
return resultTexture;
|
|
4466
|
-
}
|
|
4467
|
-
destroy() {
|
|
4468
|
-
this._renderer = null;
|
|
4469
|
-
}
|
|
4470
|
-
}
|
|
4471
|
-
/** @ignore */
|
|
4472
|
-
CanvasTextSystem.extension = {
|
|
4473
|
-
type: [
|
|
4474
|
-
ExtensionType.WebGLSystem,
|
|
4475
|
-
ExtensionType.WebGPUSystem,
|
|
4476
|
-
ExtensionType.CanvasSystem
|
|
4477
|
-
],
|
|
4478
|
-
name: "canvasText"
|
|
4479
|
-
};
|
|
4480
|
-
|
|
4481
|
-
extensions.add(ResizePlugin);
|
|
4482
|
-
extensions.add(TickerPlugin);
|
|
4483
|
-
|
|
4484
|
-
extensions.add(GraphicsPipe);
|
|
4485
|
-
extensions.add(GraphicsContextSystem);
|
|
4486
|
-
|
|
4487
|
-
extensions.add(MeshPipe);
|
|
4488
|
-
|
|
4489
|
-
extensions.add(GlParticleContainerPipe);
|
|
4490
|
-
extensions.add(GpuParticleContainerPipe);
|
|
4491
|
-
|
|
4492
|
-
extensions.add(CanvasTextSystem);
|
|
4493
|
-
extensions.add(CanvasTextPipe);
|
|
4494
|
-
|
|
4495
|
-
extensions.add(BitmapTextPipe);
|
|
4496
|
-
|
|
4497
|
-
extensions.add(HTMLTextSystem);
|
|
4498
|
-
extensions.add(HTMLTextPipe);
|
|
4499
|
-
|
|
4500
|
-
extensions.add(TilingSpritePipe);
|
|
4501
|
-
|
|
4502
|
-
extensions.add(NineSliceSpritePipe);
|
|
4503
|
-
|
|
4504
|
-
extensions.add(FilterSystem);
|
|
4505
|
-
extensions.add(FilterPipe);
|