@codexo/exojs 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +156 -0
- package/dist/esm/core/Application.d.ts +8 -1
- package/dist/esm/core/Application.js +17 -1
- package/dist/esm/core/Application.js.map +1 -1
- package/dist/esm/core/SceneManager.js +11 -12
- package/dist/esm/core/SceneManager.js.map +1 -1
- package/dist/esm/core/capabilities.d.ts +30 -29
- package/dist/esm/core/capabilities.js +163 -61
- package/dist/esm/core/capabilities.js.map +1 -1
- package/dist/esm/index.js +1 -6
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/math/geometry.d.ts +12 -8
- package/dist/esm/math/geometry.js +119 -72
- package/dist/esm/math/geometry.js.map +1 -1
- package/dist/esm/rendering/index.d.ts +0 -5
- package/dist/esm/rendering/primitives/Graphics.d.ts +3 -2
- package/dist/esm/rendering/primitives/Graphics.js +33 -25
- package/dist/esm/rendering/primitives/Graphics.js.map +1 -1
- package/dist/esm/rendering/webgl2/WebGl2Backend.js +1 -4
- package/dist/esm/rendering/webgl2/WebGl2Backend.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuBackend.js +0 -3
- package/dist/esm/rendering/webgpu/WebGpuBackend.js.map +1 -1
- package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js +1 -2
- package/dist/esm/rendering/webgpu/WebGpuMaskCompositor.js.map +1 -1
- package/dist/exo.esm.js +710 -1280
- package/dist/exo.esm.js.map +1 -1
- package/package.json +1 -1
- package/dist/esm/rendering/primitives/CircleGeometry.d.ts +0 -4
- package/dist/esm/rendering/primitives/CircleGeometry.js +0 -21
- package/dist/esm/rendering/primitives/CircleGeometry.js.map +0 -1
- package/dist/esm/rendering/primitives/DrawableShape.d.ts +0 -11
- package/dist/esm/rendering/primitives/DrawableShape.js +0 -21
- package/dist/esm/rendering/primitives/DrawableShape.js.map +0 -1
- package/dist/esm/rendering/primitives/Geometry.d.ts +0 -13
- package/dist/esm/rendering/primitives/Geometry.js +0 -16
- package/dist/esm/rendering/primitives/Geometry.js.map +0 -1
- package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.d.ts +0 -26
- package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.js +0 -222
- package/dist/esm/rendering/webgl2/WebGl2PrimitiveRenderer.js.map +0 -1
- package/dist/esm/rendering/webgl2/glsl/primitive.frag.js +0 -4
- package/dist/esm/rendering/webgl2/glsl/primitive.frag.js.map +0 -1
- package/dist/esm/rendering/webgl2/glsl/primitive.vert.js +0 -4
- package/dist/esm/rendering/webgl2/glsl/primitive.vert.js.map +0 -1
- package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.d.ts +0 -38
- package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.js +0 -488
- package/dist/esm/rendering/webgpu/WebGpuPrimitiveRenderer.js.map +0 -1
package/dist/exo.esm.js
CHANGED
|
@@ -372,6 +372,202 @@ const canvasSourceToDataUrl = (source) => {
|
|
|
372
372
|
return canvasElement.toDataURL();
|
|
373
373
|
};
|
|
374
374
|
|
|
375
|
+
/// <reference types="@webgpu/types" />
|
|
376
|
+
// Browser-environment feature detection. Construction is private; the
|
|
377
|
+
// only public entry is `Capabilities.ready`, a lazy-cached `Promise<Capabilities>`
|
|
378
|
+
// that fires the (mostly) async probes on first access and returns the
|
|
379
|
+
// same Promise for every subsequent call. Once it resolves, the returned
|
|
380
|
+
// instance is frozen — every property is read once and never mutates.
|
|
381
|
+
//
|
|
382
|
+
// Synchronous callsites should keep the resolved instance in scope (e.g.,
|
|
383
|
+
// `app.capabilities` after `await app.start(...)`); there is no global
|
|
384
|
+
// sync mirror, by design.
|
|
385
|
+
const hasWindow = typeof window !== 'undefined';
|
|
386
|
+
const hasDocument = typeof document !== 'undefined';
|
|
387
|
+
const hasNavigator = typeof navigator !== 'undefined';
|
|
388
|
+
class Capabilities {
|
|
389
|
+
static _readyPromise = null;
|
|
390
|
+
/**
|
|
391
|
+
* Lazy-cached Promise that resolves to a frozen Capabilities instance.
|
|
392
|
+
*
|
|
393
|
+
* The first read kicks off the async probes (currently just the WebGPU
|
|
394
|
+
* adapter request); every subsequent read returns the same Promise.
|
|
395
|
+
* Concurrent callers share the in-flight detection — no double work.
|
|
396
|
+
*
|
|
397
|
+
* Early-warmup pattern for callers who want to overlap detection with
|
|
398
|
+
* other startup work:
|
|
399
|
+
*
|
|
400
|
+
* ```ts
|
|
401
|
+
* void Capabilities.ready; // fire-and-forget; starts probes now
|
|
402
|
+
* // ... unrelated bootstrap ...
|
|
403
|
+
* const caps = await Capabilities.ready; // typically already resolved
|
|
404
|
+
* ```
|
|
405
|
+
*/
|
|
406
|
+
static get ready() {
|
|
407
|
+
if (Capabilities._readyPromise === null) {
|
|
408
|
+
Capabilities._readyPromise = Capabilities._detect();
|
|
409
|
+
}
|
|
410
|
+
return Capabilities._readyPromise;
|
|
411
|
+
}
|
|
412
|
+
webgl2;
|
|
413
|
+
webgpu;
|
|
414
|
+
webgpuAdapter;
|
|
415
|
+
webgpuVendor;
|
|
416
|
+
webgpuArchitecture;
|
|
417
|
+
pointer;
|
|
418
|
+
keyboard;
|
|
419
|
+
gamepad;
|
|
420
|
+
touch;
|
|
421
|
+
maxTouchPoints;
|
|
422
|
+
audio;
|
|
423
|
+
fullscreen;
|
|
424
|
+
vibration;
|
|
425
|
+
offscreenCanvas;
|
|
426
|
+
webWorkers;
|
|
427
|
+
devicePixelRatio;
|
|
428
|
+
constructor(values) {
|
|
429
|
+
this.webgl2 = values.webgl2;
|
|
430
|
+
this.webgpu = values.webgpu;
|
|
431
|
+
this.webgpuAdapter = values.webgpuAdapter;
|
|
432
|
+
this.webgpuVendor = values.webgpuVendor;
|
|
433
|
+
this.webgpuArchitecture = values.webgpuArchitecture;
|
|
434
|
+
this.pointer = values.pointer;
|
|
435
|
+
this.keyboard = values.keyboard;
|
|
436
|
+
this.gamepad = values.gamepad;
|
|
437
|
+
this.touch = values.touch;
|
|
438
|
+
this.maxTouchPoints = values.maxTouchPoints;
|
|
439
|
+
this.audio = values.audio;
|
|
440
|
+
this.fullscreen = values.fullscreen;
|
|
441
|
+
this.vibration = values.vibration;
|
|
442
|
+
this.offscreenCanvas = values.offscreenCanvas;
|
|
443
|
+
this.webWorkers = values.webWorkers;
|
|
444
|
+
this.devicePixelRatio = values.devicePixelRatio;
|
|
445
|
+
Object.freeze(this);
|
|
446
|
+
}
|
|
447
|
+
static async _detect() {
|
|
448
|
+
const [webgpuAdapter, webgpuInfo] = await probeWebGpu();
|
|
449
|
+
return new Capabilities({
|
|
450
|
+
webgl2: probeWebGl2(),
|
|
451
|
+
webgpu: probeWebGpuApiSurface(),
|
|
452
|
+
webgpuAdapter,
|
|
453
|
+
webgpuVendor: webgpuInfo?.vendor ?? null,
|
|
454
|
+
webgpuArchitecture: webgpuInfo?.architecture ?? null,
|
|
455
|
+
pointer: probePointer(),
|
|
456
|
+
keyboard: probeKeyboard(),
|
|
457
|
+
gamepad: probeGamepad(),
|
|
458
|
+
touch: probeTouchSupported(),
|
|
459
|
+
maxTouchPoints: probeMaxTouchPoints(),
|
|
460
|
+
audio: probeAudio(),
|
|
461
|
+
fullscreen: probeFullscreen(),
|
|
462
|
+
vibration: probeVibration(),
|
|
463
|
+
offscreenCanvas: probeOffscreenCanvas(),
|
|
464
|
+
webWorkers: probeWebWorkers(),
|
|
465
|
+
devicePixelRatio: hasWindow ? window.devicePixelRatio : 1,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// --- probes ---------------------------------------------------------------
|
|
470
|
+
function probeWebGl2() {
|
|
471
|
+
if (!hasDocument)
|
|
472
|
+
return false;
|
|
473
|
+
try {
|
|
474
|
+
const canvas = document.createElement('canvas');
|
|
475
|
+
const gl = canvas.getContext('webgl2');
|
|
476
|
+
return gl !== null;
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function probeWebGpuApiSurface() {
|
|
483
|
+
return hasNavigator && 'gpu' in navigator;
|
|
484
|
+
}
|
|
485
|
+
async function probeWebGpu() {
|
|
486
|
+
if (!probeWebGpuApiSurface())
|
|
487
|
+
return [null, null];
|
|
488
|
+
const gpu = navigator.gpu;
|
|
489
|
+
if (!gpu || typeof gpu.requestAdapter !== 'function')
|
|
490
|
+
return [null, null];
|
|
491
|
+
try {
|
|
492
|
+
const adapter = await gpu.requestAdapter();
|
|
493
|
+
if (!adapter)
|
|
494
|
+
return [null, null];
|
|
495
|
+
// Modern path: GPUAdapter.info is a sync property (Chrome 116+,
|
|
496
|
+
// Safari 18+). Older browsers exposed a deprecated async
|
|
497
|
+
// requestAdapterInfo() instead. Try the modern path first, fall
|
|
498
|
+
// back if needed.
|
|
499
|
+
const adapterAny = adapter;
|
|
500
|
+
if (adapterAny.info) {
|
|
501
|
+
return [adapter, adapterAny.info];
|
|
502
|
+
}
|
|
503
|
+
if (typeof adapterAny.requestAdapterInfo === 'function') {
|
|
504
|
+
try {
|
|
505
|
+
return [adapter, await adapterAny.requestAdapterInfo()];
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
return [adapter, null];
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return [adapter, null];
|
|
512
|
+
}
|
|
513
|
+
catch {
|
|
514
|
+
return [null, null];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
function probePointer() {
|
|
518
|
+
return hasWindow && 'PointerEvent' in window;
|
|
519
|
+
}
|
|
520
|
+
function probeKeyboard() {
|
|
521
|
+
return hasWindow && 'KeyboardEvent' in window;
|
|
522
|
+
}
|
|
523
|
+
function probeGamepad() {
|
|
524
|
+
return hasNavigator && typeof navigator.getGamepads === 'function';
|
|
525
|
+
}
|
|
526
|
+
function probeTouchSupported() {
|
|
527
|
+
if (!hasWindow)
|
|
528
|
+
return false;
|
|
529
|
+
if ('ontouchstart' in window)
|
|
530
|
+
return true;
|
|
531
|
+
if (probeMaxTouchPoints() > 0)
|
|
532
|
+
return true;
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
function probeMaxTouchPoints() {
|
|
536
|
+
if (!hasNavigator)
|
|
537
|
+
return 0;
|
|
538
|
+
const points = navigator.maxTouchPoints;
|
|
539
|
+
return typeof points === 'number' ? points : 0;
|
|
540
|
+
}
|
|
541
|
+
function probeAudio() {
|
|
542
|
+
if (!hasWindow)
|
|
543
|
+
return false;
|
|
544
|
+
const w = window;
|
|
545
|
+
return typeof w.AudioContext !== 'undefined' || typeof w.webkitAudioContext !== 'undefined';
|
|
546
|
+
}
|
|
547
|
+
function probeFullscreen() {
|
|
548
|
+
if (!hasDocument)
|
|
549
|
+
return false;
|
|
550
|
+
const el = document.documentElement;
|
|
551
|
+
return typeof el.requestFullscreen === 'function' || typeof el.webkitRequestFullscreen === 'function';
|
|
552
|
+
}
|
|
553
|
+
function probeVibration() {
|
|
554
|
+
return hasNavigator && typeof navigator.vibrate === 'function';
|
|
555
|
+
}
|
|
556
|
+
function probeOffscreenCanvas() {
|
|
557
|
+
// The browser global is verbatim `OffscreenCanvas`; eslint's
|
|
558
|
+
// strict-camelCase rule rejects the property name even though we
|
|
559
|
+
// can't rename a web standard.
|
|
560
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
561
|
+
return hasWindow && typeof window.OffscreenCanvas !== 'undefined';
|
|
562
|
+
}
|
|
563
|
+
function probeWebWorkers() {
|
|
564
|
+
// The browser global is verbatim `Worker`; eslint's strict-camelCase
|
|
565
|
+
// rule rejects the property name even though we can't rename a web
|
|
566
|
+
// standard.
|
|
567
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
568
|
+
return hasWindow && typeof window.Worker === 'function';
|
|
569
|
+
}
|
|
570
|
+
|
|
375
571
|
class Clock {
|
|
376
572
|
_startTime;
|
|
377
573
|
_elapsedTime = new Time(0);
|
|
@@ -3446,37 +3642,117 @@ class Drawable extends RenderNode {
|
|
|
3446
3642
|
}
|
|
3447
3643
|
}
|
|
3448
3644
|
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3645
|
+
/**
|
|
3646
|
+
* Arbitrary 2D triangle-mesh primitive.
|
|
3647
|
+
*
|
|
3648
|
+
* `Mesh` lives alongside {@link Sprite} as a public Drawable: it has the
|
|
3649
|
+
* same transform (position/rotation/scale/origin), tint, blendMode,
|
|
3650
|
+
* filters, masks, and cacheAsBitmap — but the geometry it renders is
|
|
3651
|
+
* user-supplied rather than implied by a texture frame. The intended use
|
|
3652
|
+
* cases are:
|
|
3653
|
+
*
|
|
3654
|
+
* - Custom-shape sprites whose silhouette isn't a quad (badges, speech
|
|
3655
|
+
* bubbles, region overlays).
|
|
3656
|
+
* - Deformable visuals (rope/ribbon, banner, water surface): mutate the
|
|
3657
|
+
* vertex array between frames and the GPU re-tessellates nothing —
|
|
3658
|
+
* only the transform changes per frame.
|
|
3659
|
+
* - Particles or trails with custom geometry per emitter.
|
|
3660
|
+
*
|
|
3661
|
+
* The mesh data is **immutable after construction** in v1: vertex /
|
|
3662
|
+
* index / UV / color arrays are exposed as readonly references. Mutate
|
|
3663
|
+
* the underlying typed arrays in-place if you need per-frame updates,
|
|
3664
|
+
* but the array lengths and topology cannot change. Texture is the only
|
|
3665
|
+
* post-construction mutable property.
|
|
3666
|
+
*
|
|
3667
|
+
* The vertex stream is a flat `Float32Array` of (x, y) pairs in local
|
|
3668
|
+
* space. The mesh's local bounds are computed once at construction from
|
|
3669
|
+
* the AABB of those vertices and used by the cull pass. Re-computing
|
|
3670
|
+
* after in-place mutation is the caller's responsibility (call
|
|
3671
|
+
* `recomputeLocalBounds()`).
|
|
3672
|
+
*/
|
|
3673
|
+
class Mesh extends Drawable {
|
|
3674
|
+
vertices;
|
|
3675
|
+
indices;
|
|
3676
|
+
uvs;
|
|
3677
|
+
colors;
|
|
3678
|
+
_texture;
|
|
3679
|
+
constructor(options) {
|
|
3454
3680
|
super();
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3681
|
+
const { vertices, indices = null, uvs = null, colors = null, texture = null } = options;
|
|
3682
|
+
if (vertices.length === 0 || vertices.length % 2 !== 0) {
|
|
3683
|
+
throw new Error(`Mesh vertices must be a non-empty flat array of (x,y) pairs (got length ${vertices.length}).`);
|
|
3684
|
+
}
|
|
3685
|
+
const vertexCount = vertices.length / 2;
|
|
3686
|
+
if (vertexCount < 3) {
|
|
3687
|
+
throw new Error(`Mesh requires at least 3 vertices (got ${vertexCount}).`);
|
|
3688
|
+
}
|
|
3689
|
+
if (uvs !== null && uvs.length !== vertices.length) {
|
|
3690
|
+
throw new Error(`Mesh uvs length ${uvs.length} must equal vertices length ${vertices.length}.`);
|
|
3691
|
+
}
|
|
3692
|
+
if (colors !== null && colors.length !== vertexCount) {
|
|
3693
|
+
throw new Error(`Mesh colors length ${colors.length} must equal vertex count ${vertexCount}.`);
|
|
3694
|
+
}
|
|
3695
|
+
if (indices !== null) {
|
|
3696
|
+
if (indices.length === 0 || indices.length % 3 !== 0) {
|
|
3697
|
+
throw new Error(`Mesh indices must be a non-empty multiple of 3 (got length ${indices.length}).`);
|
|
3698
|
+
}
|
|
3699
|
+
for (let i = 0; i < indices.length; i++) {
|
|
3700
|
+
if (indices[i] >= vertexCount) {
|
|
3701
|
+
throw new Error(`Mesh index ${indices[i]} at position ${i} is out of range for vertex count ${vertexCount}.`);
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
else if (vertexCount % 3 !== 0) {
|
|
3706
|
+
throw new Error(`Non-indexed Mesh requires a vertex count that is a multiple of 3 (got ${vertexCount}).`);
|
|
3707
|
+
}
|
|
3708
|
+
this.vertices = vertices;
|
|
3709
|
+
this.indices = indices;
|
|
3710
|
+
this.uvs = uvs;
|
|
3711
|
+
this.colors = colors;
|
|
3712
|
+
this._texture = texture;
|
|
3713
|
+
this.recomputeLocalBounds();
|
|
3458
3714
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
this.color.destroy();
|
|
3715
|
+
get vertexCount() {
|
|
3716
|
+
return this.vertices.length / 2;
|
|
3462
3717
|
}
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
class Geometry {
|
|
3466
|
-
vertices;
|
|
3467
|
-
indices;
|
|
3468
|
-
points;
|
|
3469
|
-
constructor({ vertices = [], indices = [], points = [], } = {}) {
|
|
3470
|
-
this.vertices = new Float32Array(vertices);
|
|
3471
|
-
this.indices = new Uint16Array(indices);
|
|
3472
|
-
this.points = points;
|
|
3718
|
+
get indexCount() {
|
|
3719
|
+
return this.indices?.length ?? this.vertexCount;
|
|
3473
3720
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3721
|
+
get texture() {
|
|
3722
|
+
return this._texture;
|
|
3723
|
+
}
|
|
3724
|
+
set texture(texture) {
|
|
3725
|
+
this._texture = texture;
|
|
3726
|
+
this.invalidateCache();
|
|
3727
|
+
}
|
|
3728
|
+
/**
|
|
3729
|
+
* Recompute the local AABB from the current vertex array. Call after
|
|
3730
|
+
* mutating `vertices` in place to keep culling correct; otherwise the
|
|
3731
|
+
* bounds the cull pass sees will be the AABB at construction time.
|
|
3732
|
+
*/
|
|
3733
|
+
recomputeLocalBounds() {
|
|
3734
|
+
let minX = Infinity;
|
|
3735
|
+
let minY = Infinity;
|
|
3736
|
+
let maxX = -Infinity;
|
|
3737
|
+
let maxY = -Infinity;
|
|
3738
|
+
for (let i = 0; i < this.vertices.length; i += 2) {
|
|
3739
|
+
const x = this.vertices[i];
|
|
3740
|
+
const y = this.vertices[i + 1];
|
|
3741
|
+
if (x < minX)
|
|
3742
|
+
minX = x;
|
|
3743
|
+
if (x > maxX)
|
|
3744
|
+
maxX = x;
|
|
3745
|
+
if (y < minY)
|
|
3746
|
+
minY = y;
|
|
3747
|
+
if (y > maxY)
|
|
3748
|
+
maxY = y;
|
|
3749
|
+
}
|
|
3750
|
+
this.localBounds.set(minX, minY, maxX - minX, maxY - minY);
|
|
3751
|
+
return this;
|
|
3476
3752
|
}
|
|
3477
3753
|
}
|
|
3478
3754
|
|
|
3479
|
-
class
|
|
3755
|
+
class TransitionOverlayMesh extends Mesh {
|
|
3480
3756
|
render(backend) {
|
|
3481
3757
|
if (this.visible) {
|
|
3482
3758
|
backend.draw(this);
|
|
@@ -3484,16 +3760,16 @@ class TransitionOverlayShape extends DrawableShape {
|
|
|
3484
3760
|
return this;
|
|
3485
3761
|
}
|
|
3486
3762
|
}
|
|
3487
|
-
const
|
|
3488
|
-
vertices
|
|
3489
|
-
|
|
3490
|
-
|
|
3763
|
+
const createOverlayMesh = () => new TransitionOverlayMesh({
|
|
3764
|
+
// 4 vertices (TL, TR, BL, BR) with 2 indexed triangles forming a screen quad.
|
|
3765
|
+
vertices: new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]),
|
|
3766
|
+
indices: new Uint16Array([0, 1, 2, 1, 3, 2]),
|
|
3491
3767
|
});
|
|
3492
3768
|
const defaultFadeTransitionDuration = 220;
|
|
3493
3769
|
class SceneManager {
|
|
3494
3770
|
_app;
|
|
3495
3771
|
_stack = [];
|
|
3496
|
-
_transitionOverlay =
|
|
3772
|
+
_transitionOverlay = createOverlayMesh();
|
|
3497
3773
|
_transition = null;
|
|
3498
3774
|
onChangeScene = new Signal();
|
|
3499
3775
|
onStartScene = new Signal();
|
|
@@ -3856,7 +4132,8 @@ class SceneManager {
|
|
|
3856
4132
|
const overlayColor = transition ? transition.color : Color.black;
|
|
3857
4133
|
const backend = this._app.backend;
|
|
3858
4134
|
const bounds = backend.view.getBounds();
|
|
3859
|
-
const
|
|
4135
|
+
const overlay = this._transitionOverlay;
|
|
4136
|
+
const vertices = overlay.vertices;
|
|
3860
4137
|
vertices[0] = bounds.left;
|
|
3861
4138
|
vertices[1] = bounds.top;
|
|
3862
4139
|
vertices[2] = bounds.right;
|
|
@@ -3865,8 +4142,8 @@ class SceneManager {
|
|
|
3865
4142
|
vertices[5] = bounds.bottom;
|
|
3866
4143
|
vertices[6] = bounds.right;
|
|
3867
4144
|
vertices[7] = bounds.bottom;
|
|
3868
|
-
|
|
3869
|
-
|
|
4145
|
+
overlay.tint.set(overlayColor.r, overlayColor.g, overlayColor.b, Math.max(0, Math.min(1, alpha)));
|
|
4146
|
+
overlay.render(backend);
|
|
3870
4147
|
}
|
|
3871
4148
|
}
|
|
3872
4149
|
|
|
@@ -5638,9 +5915,9 @@ class WebGl2VertexArrayObject {
|
|
|
5638
5915
|
}
|
|
5639
5916
|
}
|
|
5640
5917
|
|
|
5641
|
-
var vertexSource$
|
|
5918
|
+
var vertexSource$3 = "#version 300 es\nprecision lowp float;\nprecision lowp int;\n\n// Per-instance attributes (divisor = 1). Each Sprite contributes one entry\n// to the per-instance buffer; gl_VertexID 0..3 selects which corner of the\n// quad this invocation is computing.\nlayout(location = 0) in vec4 a_localBounds; // left, top, right, bottom (local space)\nlayout(location = 1) in vec3 a_transformAB; // a, b, x — first row of 2D affine\nlayout(location = 2) in vec3 a_transformCD; // c, d, y — second row\nlayout(location = 3) in vec4 a_uvBounds; // uMin, vMin, uMax, vMax (normalised, already flipY-swapped)\nlayout(location = 4) in vec4 a_color; // RGBA tint\nlayout(location = 5) in uint a_textureSlot;\n\nuniform mat3 u_projection;\n\nout vec2 v_texcoord;\nout vec4 v_color;\nflat out uint v_textureSlot;\n\nvoid main(void) {\n // gl_VertexID 0..3 → corner: 0=TL, 1=TR, 2=BL, 3=BR (TRIANGLE_STRIP order)\n int vid = gl_VertexID;\n int cornerX = vid & 1;\n int cornerY = (vid >> 1) & 1;\n\n // Local-space corner: pick from the bounds rectangle.\n float localX = (cornerX == 0) ? a_localBounds.x : a_localBounds.z;\n float localY = (cornerY == 0) ? a_localBounds.y : a_localBounds.w;\n\n // Apply the per-instance affine transform: world = M * (localX, localY, 1)\n float worldX = (a_transformAB.x * localX) + (a_transformAB.y * localY) + a_transformAB.z;\n float worldY = (a_transformCD.x * localX) + (a_transformCD.y * localY) + a_transformCD.z;\n\n gl_Position = vec4((u_projection * vec3(worldX, worldY, 1.0)).xy, 0.0, 1.0);\n\n // UV: pick from the bounds rectangle. The CPU pre-swaps Y bounds when\n // the texture is flipY, so the shader doesn't have to know.\n float u = (cornerX == 0) ? a_uvBounds.x : a_uvBounds.z;\n float v = (cornerY == 0) ? a_uvBounds.y : a_uvBounds.w;\n v_texcoord = vec2(u, v);\n\n v_color = vec4(a_color.rgb * a_color.a, a_color.a);\n v_textureSlot = a_textureSlot;\n}\n";
|
|
5642
5919
|
|
|
5643
|
-
var fragmentSource$
|
|
5920
|
+
var fragmentSource$3 = "#version 300 es\nprecision lowp float;\nprecision lowp int;\n\n// Multi-texture sprite batching: up to 8 textures bound per draw call,\n// each fragment selects its source via a flat-interpolated slot index.\n//\n// GLSL ES 3.0 forbids non-constant array-of-sampler indexing unless the\n// expression is dynamically uniform — which a per-vertex slot is not\n// once different triangles in the same batch carry different slots. The\n// if/else chain below dispatches statically and dodges that constraint.\n\nuniform sampler2D u_texture0;\nuniform sampler2D u_texture1;\nuniform sampler2D u_texture2;\nuniform sampler2D u_texture3;\nuniform sampler2D u_texture4;\nuniform sampler2D u_texture5;\nuniform sampler2D u_texture6;\nuniform sampler2D u_texture7;\n\nin vec2 v_texcoord;\nin vec4 v_color;\nflat in uint v_textureSlot;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n vec4 sampleColor;\n\n if (v_textureSlot == 0u) {\n sampleColor = texture(u_texture0, v_texcoord);\n } else if (v_textureSlot == 1u) {\n sampleColor = texture(u_texture1, v_texcoord);\n } else if (v_textureSlot == 2u) {\n sampleColor = texture(u_texture2, v_texcoord);\n } else if (v_textureSlot == 3u) {\n sampleColor = texture(u_texture3, v_texcoord);\n } else if (v_textureSlot == 4u) {\n sampleColor = texture(u_texture4, v_texcoord);\n } else if (v_textureSlot == 5u) {\n sampleColor = texture(u_texture5, v_texcoord);\n } else if (v_textureSlot == 6u) {\n sampleColor = texture(u_texture6, v_texcoord);\n } else {\n sampleColor = texture(u_texture7, v_texcoord);\n }\n\n fragColor = sampleColor * v_color;\n}\n";
|
|
5644
5921
|
|
|
5645
5922
|
/**
|
|
5646
5923
|
* Instanced sprite renderer for WebGL2.
|
|
@@ -5688,7 +5965,7 @@ class WebGl2SpriteRenderer extends AbstractWebGl2Renderer {
|
|
|
5688
5965
|
constructor(batchSize) {
|
|
5689
5966
|
super();
|
|
5690
5967
|
this._batchSize = batchSize;
|
|
5691
|
-
this._shader = new Shader(vertexSource$
|
|
5968
|
+
this._shader = new Shader(vertexSource$3, fragmentSource$3);
|
|
5692
5969
|
this._instanceData = new ArrayBuffer(batchSize * instanceStrideBytes$3);
|
|
5693
5970
|
this._instanceFloat32 = new Float32Array(this._instanceData);
|
|
5694
5971
|
this._instanceUint32 = new Uint32Array(this._instanceData);
|
|
@@ -6113,26 +6390,26 @@ class Texture {
|
|
|
6113
6390
|
}
|
|
6114
6391
|
}
|
|
6115
6392
|
|
|
6116
|
-
var vertexSource$
|
|
6393
|
+
var vertexSource$2 = "#version 300 es\nprecision lowp float;\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\nlayout(location = 2) in vec4 a_color;\n\nuniform mat3 u_projection;\nuniform mat3 u_translation;\n\nout vec2 v_texcoord;\nout vec4 v_color;\n\nvoid main(void) {\n gl_Position = vec4((u_projection * u_translation * vec3(a_position, 1.0)).xy, 0.0, 1.0);\n v_texcoord = a_texcoord;\n v_color = a_color;\n}\n";
|
|
6117
6394
|
|
|
6118
|
-
var fragmentSource$
|
|
6395
|
+
var fragmentSource$2 = "#version 300 es\nprecision lowp float;\n\nuniform sampler2D u_texture;\nuniform vec4 u_tint;\n\nin vec2 v_texcoord;\nin vec4 v_color;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n vec4 base = texture(u_texture, v_texcoord) * v_color * u_tint;\n fragColor = vec4(base.rgb * base.a, base.a);\n}\n";
|
|
6119
6396
|
|
|
6120
6397
|
// Per-vertex layout (20 bytes):
|
|
6121
6398
|
// position: vec2 f32 (offset 0, 8 bytes)
|
|
6122
6399
|
// texcoord: vec2 f32 (offset 8, 8 bytes)
|
|
6123
6400
|
// color: u8x4 norm (offset 16, 4 bytes)
|
|
6124
|
-
const vertexStrideBytes$
|
|
6125
|
-
const vertexStrideWords
|
|
6401
|
+
const vertexStrideBytes$3 = 20;
|
|
6402
|
+
const vertexStrideWords = vertexStrideBytes$3 / 4;
|
|
6126
6403
|
const initialVertexCapacity = 64;
|
|
6127
6404
|
const initialIndexCapacity = 192;
|
|
6128
6405
|
const defaultVertexColor = 0xFFFFFFFF; // white, full alpha
|
|
6129
6406
|
class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
6130
|
-
_shader = new Shader(vertexSource$
|
|
6407
|
+
_shader = new Shader(vertexSource$2, fragmentSource$2);
|
|
6131
6408
|
_tintScratch = new Float32Array(4);
|
|
6132
6409
|
_textureUnitScratch = new Int32Array([0]);
|
|
6133
6410
|
_vertexCapacity = initialVertexCapacity;
|
|
6134
6411
|
_indexCapacity = initialIndexCapacity;
|
|
6135
|
-
_vertexData = new ArrayBuffer(initialVertexCapacity * vertexStrideBytes$
|
|
6412
|
+
_vertexData = new ArrayBuffer(initialVertexCapacity * vertexStrideBytes$3);
|
|
6136
6413
|
_float32View = new Float32Array(this._vertexData);
|
|
6137
6414
|
_uint32View = new Uint32Array(this._vertexData);
|
|
6138
6415
|
_indexData = new Uint16Array(initialIndexCapacity);
|
|
@@ -6179,7 +6456,7 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6179
6456
|
const uvs = mesh.uvs;
|
|
6180
6457
|
const colors = mesh.colors;
|
|
6181
6458
|
for (let i = 0; i < vertexCount; i++) {
|
|
6182
|
-
const word = i * vertexStrideWords
|
|
6459
|
+
const word = i * vertexStrideWords;
|
|
6183
6460
|
const pair = i * 2;
|
|
6184
6461
|
this._float32View[word] = positions[pair];
|
|
6185
6462
|
this._float32View[word + 1] = positions[pair + 1];
|
|
@@ -6205,7 +6482,7 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6205
6482
|
}
|
|
6206
6483
|
this._shader.sync();
|
|
6207
6484
|
backend.bindVertexArrayObject(connection.vao);
|
|
6208
|
-
connection.vertexBuffer.upload(this._float32View.subarray(0, vertexCount * vertexStrideWords
|
|
6485
|
+
connection.vertexBuffer.upload(this._float32View.subarray(0, vertexCount * vertexStrideWords));
|
|
6209
6486
|
connection.indexBuffer.upload(this._indexData.subarray(0, indexCount));
|
|
6210
6487
|
connection.vao.draw(indexCount, 0, RenderingPrimitives.Triangles);
|
|
6211
6488
|
backend.stats.batches++;
|
|
@@ -6238,9 +6515,9 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6238
6515
|
this._shader.sync();
|
|
6239
6516
|
const vao = new WebGl2VertexArrayObject()
|
|
6240
6517
|
.addIndex(indexBuffer)
|
|
6241
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$
|
|
6242
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes$
|
|
6243
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_color'), gl.UNSIGNED_BYTE, true, vertexStrideBytes$
|
|
6518
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$3, 0)
|
|
6519
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes$3, 8)
|
|
6520
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_color'), gl.UNSIGNED_BYTE, true, vertexStrideBytes$3, 16)
|
|
6244
6521
|
.connect(this._createVaoRuntime(gl, vaoHandle));
|
|
6245
6522
|
this._connection = { gl, vao, vertexBuffer, indexBuffer };
|
|
6246
6523
|
}
|
|
@@ -6265,7 +6542,7 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6265
6542
|
while (this._vertexCapacity < vertexCount) {
|
|
6266
6543
|
this._vertexCapacity *= 2;
|
|
6267
6544
|
}
|
|
6268
|
-
this._vertexData = new ArrayBuffer(this._vertexCapacity * vertexStrideBytes$
|
|
6545
|
+
this._vertexData = new ArrayBuffer(this._vertexCapacity * vertexStrideBytes$3);
|
|
6269
6546
|
this._float32View = new Float32Array(this._vertexData);
|
|
6270
6547
|
this._uint32View = new Uint32Array(this._vertexData);
|
|
6271
6548
|
}
|
|
@@ -6347,9 +6624,9 @@ class WebGl2MeshRenderer extends AbstractWebGl2Renderer {
|
|
|
6347
6624
|
}
|
|
6348
6625
|
}
|
|
6349
6626
|
|
|
6350
|
-
var vertexSource$
|
|
6627
|
+
var vertexSource$1 = "#version 300 es\nprecision lowp float;\nprecision lowp int;\n\n// Per-instance attributes (one entry per particle, 24 bytes total).\nlayout(location = 0) in vec2 a_translation; // particle position in system-local space\nlayout(location = 1) in vec2 a_scale; // particle scale\nlayout(location = 2) in float a_rotation; // particle rotation in degrees\nlayout(location = 3) in vec4 a_color; // RGBA tint\n\nuniform mat3 u_projection;\nuniform mat3 u_systemTransform;\nuniform vec4 u_localBounds; // left, top, right, bottom (system.vertices)\nuniform vec4 u_uvBounds; // uMin, vMin, uMax, vMax (flipY-swapped)\n\nout vec2 v_texcoord;\nout vec4 v_color;\n\nvoid main(void) {\n // Static index buffer is [0,1,2,0,2,3] (triangle-list), so gl_VertexID 0..3\n // maps to TL, TR, BR, BL via the same bit math the sprite renderer uses.\n int vid = gl_VertexID;\n int cornerX = ((vid + 1) >> 1) & 1;\n int cornerY = vid >> 1;\n\n float localX = (cornerX == 0) ? u_localBounds.x : u_localBounds.z;\n float localY = (cornerY == 0) ? u_localBounds.y : u_localBounds.w;\n\n // Per-particle scale + rotation.\n vec2 rotation = vec2(sin(radians(a_rotation)), cos(radians(a_rotation)));\n vec2 transformed = vec2(\n (localX * (a_scale.x * rotation.y)) + (localY * (a_scale.y * rotation.x)),\n (localX * (a_scale.x * -rotation.x)) + (localY * (a_scale.y * rotation.y))\n );\n\n vec3 worldPos = vec3(transformed + a_translation, 1.0);\n\n gl_Position = vec4((u_projection * u_systemTransform * worldPos).xy, 0.0, 1.0);\n\n float u = (cornerX == 0) ? u_uvBounds.x : u_uvBounds.z;\n float v = (cornerY == 0) ? u_uvBounds.y : u_uvBounds.w;\n v_texcoord = vec2(u, v);\n\n v_color = vec4(a_color.rgb * a_color.a, a_color.a);\n}\n";
|
|
6351
6628
|
|
|
6352
|
-
var fragmentSource$
|
|
6629
|
+
var fragmentSource$1 = "#version 300 es\nprecision lowp float;\n\nuniform sampler2D u_texture;\n\nin vec2 v_texcoord;\nin vec4 v_color;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n fragColor = texture(u_texture, v_texcoord) * v_color;\n}\n";
|
|
6353
6630
|
|
|
6354
6631
|
/**
|
|
6355
6632
|
* Instanced particle renderer for WebGL2.
|
|
@@ -6401,7 +6678,7 @@ class WebGl2ParticleRenderer extends AbstractWebGl2Renderer {
|
|
|
6401
6678
|
constructor(batchSize) {
|
|
6402
6679
|
super();
|
|
6403
6680
|
this._batchSize = batchSize;
|
|
6404
|
-
this._shader = new Shader(vertexSource$
|
|
6681
|
+
this._shader = new Shader(vertexSource$1, fragmentSource$1);
|
|
6405
6682
|
this._instanceData = new ArrayBuffer(batchSize * instanceStrideBytes$2);
|
|
6406
6683
|
this._instanceFloat32 = new Float32Array(this._instanceData);
|
|
6407
6684
|
this._instanceUint32 = new Uint32Array(this._instanceData);
|
|
@@ -6641,277 +6918,62 @@ class WebGl2ParticleRenderer extends AbstractWebGl2Renderer {
|
|
|
6641
6918
|
}
|
|
6642
6919
|
}
|
|
6643
6920
|
|
|
6644
|
-
var vertexSource
|
|
6645
|
-
|
|
6646
|
-
var fragmentSource
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
const vertexStrideBytes$
|
|
6650
|
-
const
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6921
|
+
var vertexSource = "#version 300 es\nprecision lowp float;\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nuniform mat3 u_projection;\n\nout vec2 v_texcoord;\n\nvoid main(void) {\n gl_Position = vec4((u_projection * vec3(a_position, 1.0)).xy, 0.0, 1.0);\n v_texcoord = a_texcoord;\n}\n";
|
|
6922
|
+
|
|
6923
|
+
var fragmentSource = "#version 300 es\nprecision lowp float;\n\nuniform sampler2D u_content;\nuniform sampler2D u_mask;\n\nin vec2 v_texcoord;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n vec4 contentColor = texture(u_content, v_texcoord);\n float maskAlpha = texture(u_mask, v_texcoord).a;\n\n fragColor = vec4(contentColor.rgb * maskAlpha, contentColor.a * maskAlpha);\n}\n";
|
|
6924
|
+
|
|
6925
|
+
// 4 floats per vertex: position(x, y) + texcoord(u, v).
|
|
6926
|
+
const vertexStrideBytes$2 = 16;
|
|
6927
|
+
const quadIndices$1 = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
|
6928
|
+
/**
|
|
6929
|
+
* Single-quad two-texture compositor used by `WebGl2Backend.composeWithAlphaMask`.
|
|
6930
|
+
*
|
|
6931
|
+
* Renders the content texture onto the active render target with each
|
|
6932
|
+
* output texel's alpha multiplied by the mask texture's alpha at the
|
|
6933
|
+
* same UV. Both textures are sampled with stretched-fit UVs over the
|
|
6934
|
+
* destination rectangle.
|
|
6935
|
+
*
|
|
6936
|
+
* Intentionally not a {@link AbstractWebGl2Renderer} subclass: this
|
|
6937
|
+
* compositor is invoked directly by the manager for non-Drawable
|
|
6938
|
+
* compositing operations and never participates in the renderer
|
|
6939
|
+
* registry dispatch path.
|
|
6940
|
+
*/
|
|
6941
|
+
class WebGl2MaskCompositor {
|
|
6942
|
+
_shader = new Shader(vertexSource, fragmentSource);
|
|
6943
|
+
_vertexData = new ArrayBuffer(4 * vertexStrideBytes$2);
|
|
6944
|
+
_float32View = new Float32Array(this._vertexData);
|
|
6945
|
+
_contentSamplerSlot = new Int32Array([0]);
|
|
6946
|
+
_maskSamplerSlot = new Int32Array([1]);
|
|
6659
6947
|
_connection = null;
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
this.
|
|
6670
|
-
|
|
6948
|
+
connect(backend) {
|
|
6949
|
+
if (this._connection !== null) {
|
|
6950
|
+
return;
|
|
6951
|
+
}
|
|
6952
|
+
const gl = backend.context;
|
|
6953
|
+
const vaoHandle = gl.createVertexArray();
|
|
6954
|
+
if (vaoHandle === null) {
|
|
6955
|
+
throw new Error('WebGl2MaskCompositor: could not create vertex array object.');
|
|
6956
|
+
}
|
|
6957
|
+
this._shader.connect(createWebGl2ShaderProgram(gl));
|
|
6958
|
+
const bufferHandles = new Map();
|
|
6959
|
+
const indexBuffer = new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, quadIndices$1, BufferUsage.StaticDraw)
|
|
6960
|
+
.connect(this._createBufferRuntime(gl, bufferHandles));
|
|
6961
|
+
const vertexBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, this._vertexData, BufferUsage.DynamicDraw)
|
|
6962
|
+
.connect(this._createBufferRuntime(gl, bufferHandles));
|
|
6963
|
+
// Force shader finalize so getAttribute() below sees a populated
|
|
6964
|
+
// attribute table; the async-compile path defers extraction until
|
|
6965
|
+
// the first sync() call.
|
|
6966
|
+
this._shader.sync();
|
|
6967
|
+
const vao = new WebGl2VertexArrayObject()
|
|
6968
|
+
.addIndex(indexBuffer)
|
|
6969
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$2, 0)
|
|
6970
|
+
.addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes$2, 8)
|
|
6971
|
+
.connect(this._createVaoRuntime(gl, vaoHandle));
|
|
6972
|
+
this._connection = { gl, vaoHandle, vao, indexBuffer, vertexBuffer, bufferHandles };
|
|
6671
6973
|
}
|
|
6672
|
-
|
|
6974
|
+
disconnect() {
|
|
6673
6975
|
const connection = this._connection;
|
|
6674
|
-
if (
|
|
6675
|
-
throw new Error('Renderer not connected');
|
|
6676
|
-
}
|
|
6677
|
-
const backend = this.getBackend();
|
|
6678
|
-
const { geometry, drawMode, color, blendMode } = shape;
|
|
6679
|
-
const vertices = geometry.vertices;
|
|
6680
|
-
const sourceIndices = geometry.indices;
|
|
6681
|
-
const vertexCount = vertices.length / 2;
|
|
6682
|
-
const indexCount = sourceIndices.length > 0 ? sourceIndices.length : vertexCount;
|
|
6683
|
-
if (vertexCount === 0 || indexCount === 0) {
|
|
6684
|
-
return;
|
|
6685
|
-
}
|
|
6686
|
-
this._ensureVertexCapacity(vertexCount);
|
|
6687
|
-
this._ensureIndexCapacity(indexCount);
|
|
6688
|
-
if (blendMode !== this._currentBlendMode) {
|
|
6689
|
-
this._currentBlendMode = blendMode;
|
|
6690
|
-
backend.setBlendMode(blendMode);
|
|
6691
|
-
}
|
|
6692
|
-
const view = backend.view;
|
|
6693
|
-
if (this._currentView !== view || this._viewId !== view.updateId) {
|
|
6694
|
-
this._currentView = view;
|
|
6695
|
-
this._viewId = view.updateId;
|
|
6696
|
-
this._shader.getUniform('u_projection').setValue(view.getTransform().toArray(false));
|
|
6697
|
-
}
|
|
6698
|
-
this._shader.getUniform('u_translation').setValue(shape.getGlobalTransform().toArray(false));
|
|
6699
|
-
const packedColor = color.toRgba();
|
|
6700
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
6701
|
-
const sourceIndex = i * 2;
|
|
6702
|
-
const targetIndex = i * vertexStrideWords;
|
|
6703
|
-
this._float32View[targetIndex] = vertices[sourceIndex];
|
|
6704
|
-
this._float32View[targetIndex + 1] = vertices[sourceIndex + 1];
|
|
6705
|
-
this._uint32View[targetIndex + 2] = packedColor;
|
|
6706
|
-
}
|
|
6707
|
-
if (sourceIndices.length > 0) {
|
|
6708
|
-
this._indexData.set(sourceIndices, 0);
|
|
6709
|
-
}
|
|
6710
|
-
else {
|
|
6711
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
6712
|
-
this._indexData[i] = i;
|
|
6713
|
-
}
|
|
6714
|
-
}
|
|
6715
|
-
this._shader.sync();
|
|
6716
|
-
backend.bindVertexArrayObject(connection.vao);
|
|
6717
|
-
connection.vertexBuffer.upload(this._float32View.subarray(0, vertexCount * vertexStrideWords));
|
|
6718
|
-
connection.indexBuffer.upload(this._indexData.subarray(0, indexCount));
|
|
6719
|
-
connection.vao.draw(indexCount, 0, drawMode);
|
|
6720
|
-
backend.stats.batches++;
|
|
6721
|
-
backend.stats.drawCalls++;
|
|
6722
|
-
}
|
|
6723
|
-
flush() {
|
|
6724
|
-
// Primitive rendering is immediate per shape in this bridge stage.
|
|
6725
|
-
}
|
|
6726
|
-
destroy() {
|
|
6727
|
-
this.disconnect();
|
|
6728
|
-
this._shader.destroy();
|
|
6729
|
-
this._currentBlendMode = null;
|
|
6730
|
-
this._currentView = null;
|
|
6731
|
-
}
|
|
6732
|
-
onConnect(backend) {
|
|
6733
|
-
const gl = backend.context;
|
|
6734
|
-
const vaoHandle = gl.createVertexArray();
|
|
6735
|
-
this._shader.connect(createWebGl2ShaderProgram(gl));
|
|
6736
|
-
if (vaoHandle === null) {
|
|
6737
|
-
throw new Error('Could not create vertex array object.');
|
|
6738
|
-
}
|
|
6739
|
-
const buffers = new Map();
|
|
6740
|
-
const indexBuffer = new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, this._indexData, BufferUsage.DynamicDraw)
|
|
6741
|
-
.connect(this._createBufferRuntime(gl, buffers));
|
|
6742
|
-
const vertexBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, this._vertexData, BufferUsage.DynamicDraw)
|
|
6743
|
-
.connect(this._createBufferRuntime(gl, buffers));
|
|
6744
|
-
// Force shader finalize so the attribute table is populated. The
|
|
6745
|
-
// async-compile path defers attribute extraction from initialize()
|
|
6746
|
-
// to first sync(); without this nudge, getAttribute() below would
|
|
6747
|
-
// throw "Attribute 'a_position' is not available".
|
|
6748
|
-
this._shader.sync();
|
|
6749
|
-
const vao = new WebGl2VertexArrayObject()
|
|
6750
|
-
.addIndex(indexBuffer)
|
|
6751
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$4, 0)
|
|
6752
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_color'), gl.UNSIGNED_BYTE, true, vertexStrideBytes$4, 8)
|
|
6753
|
-
.connect(this._createVaoRuntime(gl, vaoHandle));
|
|
6754
|
-
this._connection = { gl, buffers, vaoHandle, vao, indexBuffer, vertexBuffer };
|
|
6755
|
-
}
|
|
6756
|
-
onDisconnect() {
|
|
6757
|
-
const connection = this._connection;
|
|
6758
|
-
if (!connection) {
|
|
6759
|
-
return;
|
|
6760
|
-
}
|
|
6761
|
-
this._shader.disconnect();
|
|
6762
|
-
connection.indexBuffer.destroy();
|
|
6763
|
-
connection.vertexBuffer.destroy();
|
|
6764
|
-
connection.vao.destroy();
|
|
6765
|
-
this._connection = null;
|
|
6766
|
-
this._currentBlendMode = null;
|
|
6767
|
-
this._currentView = null;
|
|
6768
|
-
this._viewId = -1;
|
|
6769
|
-
}
|
|
6770
|
-
_ensureVertexCapacity(vertexCount) {
|
|
6771
|
-
if (vertexCount <= this._vertexCapacity) {
|
|
6772
|
-
return;
|
|
6773
|
-
}
|
|
6774
|
-
while (this._vertexCapacity < vertexCount) {
|
|
6775
|
-
this._vertexCapacity *= 2;
|
|
6776
|
-
}
|
|
6777
|
-
this._vertexData = new ArrayBuffer(this._vertexCapacity * vertexStrideBytes$4);
|
|
6778
|
-
this._float32View = new Float32Array(this._vertexData);
|
|
6779
|
-
this._uint32View = new Uint32Array(this._vertexData);
|
|
6780
|
-
}
|
|
6781
|
-
_ensureIndexCapacity(indexCount) {
|
|
6782
|
-
if (indexCount <= this._indexCapacity) {
|
|
6783
|
-
return;
|
|
6784
|
-
}
|
|
6785
|
-
while (this._indexCapacity < indexCount) {
|
|
6786
|
-
this._indexCapacity *= 2;
|
|
6787
|
-
}
|
|
6788
|
-
this._indexData = new Uint16Array(this._indexCapacity);
|
|
6789
|
-
}
|
|
6790
|
-
_createBufferRuntime(gl, buffers) {
|
|
6791
|
-
const handle = gl.createBuffer();
|
|
6792
|
-
if (handle === null) {
|
|
6793
|
-
throw new Error('Could not create render buffer.');
|
|
6794
|
-
}
|
|
6795
|
-
return {
|
|
6796
|
-
bind: (buffer) => {
|
|
6797
|
-
gl.bindBuffer(buffer.type, handle);
|
|
6798
|
-
},
|
|
6799
|
-
upload: (buffer, offset) => {
|
|
6800
|
-
const state = buffers.get(buffer);
|
|
6801
|
-
const data = buffer.data;
|
|
6802
|
-
gl.bindBuffer(buffer.type, handle);
|
|
6803
|
-
if (state && state.dataByteLength >= data.byteLength) {
|
|
6804
|
-
gl.bufferSubData(buffer.type, offset, data);
|
|
6805
|
-
state.dataByteLength = data.byteLength;
|
|
6806
|
-
}
|
|
6807
|
-
else {
|
|
6808
|
-
gl.bufferData(buffer.type, data, buffer.usage);
|
|
6809
|
-
buffers.set(buffer, { handle, dataByteLength: data.byteLength });
|
|
6810
|
-
}
|
|
6811
|
-
},
|
|
6812
|
-
destroy: (buffer) => {
|
|
6813
|
-
gl.deleteBuffer(handle);
|
|
6814
|
-
buffers.delete(buffer);
|
|
6815
|
-
buffer.disconnect();
|
|
6816
|
-
},
|
|
6817
|
-
};
|
|
6818
|
-
}
|
|
6819
|
-
_createVaoRuntime(gl, vaoHandle) {
|
|
6820
|
-
let appliedVersion = -1;
|
|
6821
|
-
return {
|
|
6822
|
-
bind: (vao) => {
|
|
6823
|
-
gl.bindVertexArray(vaoHandle);
|
|
6824
|
-
if (appliedVersion !== vao.version) {
|
|
6825
|
-
let lastBuffer = null;
|
|
6826
|
-
for (const attribute of vao.attributes) {
|
|
6827
|
-
if (lastBuffer !== attribute.buffer) {
|
|
6828
|
-
attribute.buffer.bind();
|
|
6829
|
-
lastBuffer = attribute.buffer;
|
|
6830
|
-
}
|
|
6831
|
-
gl.vertexAttribPointer(attribute.location, attribute.size, attribute.type, attribute.normalized, attribute.stride, attribute.start);
|
|
6832
|
-
gl.enableVertexAttribArray(attribute.location);
|
|
6833
|
-
}
|
|
6834
|
-
if (vao.indexBuffer) {
|
|
6835
|
-
vao.indexBuffer.bind();
|
|
6836
|
-
}
|
|
6837
|
-
appliedVersion = vao.version;
|
|
6838
|
-
}
|
|
6839
|
-
},
|
|
6840
|
-
unbind: () => {
|
|
6841
|
-
gl.bindVertexArray(null);
|
|
6842
|
-
},
|
|
6843
|
-
draw: (vao, size, start, type) => {
|
|
6844
|
-
if (vao.indexBuffer) {
|
|
6845
|
-
gl.drawElements(type, size, gl.UNSIGNED_SHORT, start);
|
|
6846
|
-
}
|
|
6847
|
-
else {
|
|
6848
|
-
gl.drawArrays(type, start, size);
|
|
6849
|
-
}
|
|
6850
|
-
},
|
|
6851
|
-
destroy: (vao) => {
|
|
6852
|
-
gl.deleteVertexArray(vaoHandle);
|
|
6853
|
-
vao.disconnect();
|
|
6854
|
-
},
|
|
6855
|
-
};
|
|
6856
|
-
}
|
|
6857
|
-
}
|
|
6858
|
-
|
|
6859
|
-
var vertexSource = "#version 300 es\nprecision lowp float;\n\nlayout(location = 0) in vec2 a_position;\nlayout(location = 1) in vec2 a_texcoord;\n\nuniform mat3 u_projection;\n\nout vec2 v_texcoord;\n\nvoid main(void) {\n gl_Position = vec4((u_projection * vec3(a_position, 1.0)).xy, 0.0, 1.0);\n v_texcoord = a_texcoord;\n}\n";
|
|
6860
|
-
|
|
6861
|
-
var fragmentSource = "#version 300 es\nprecision lowp float;\n\nuniform sampler2D u_content;\nuniform sampler2D u_mask;\n\nin vec2 v_texcoord;\n\nlayout(location = 0) out vec4 fragColor;\n\nvoid main(void) {\n vec4 contentColor = texture(u_content, v_texcoord);\n float maskAlpha = texture(u_mask, v_texcoord).a;\n\n fragColor = vec4(contentColor.rgb * maskAlpha, contentColor.a * maskAlpha);\n}\n";
|
|
6862
|
-
|
|
6863
|
-
// 4 floats per vertex: position(x, y) + texcoord(u, v).
|
|
6864
|
-
const vertexStrideBytes$3 = 16;
|
|
6865
|
-
const quadIndices$1 = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
|
6866
|
-
/**
|
|
6867
|
-
* Single-quad two-texture compositor used by `WebGl2Backend.composeWithAlphaMask`.
|
|
6868
|
-
*
|
|
6869
|
-
* Renders the content texture onto the active render target with each
|
|
6870
|
-
* output texel's alpha multiplied by the mask texture's alpha at the
|
|
6871
|
-
* same UV. Both textures are sampled with stretched-fit UVs over the
|
|
6872
|
-
* destination rectangle.
|
|
6873
|
-
*
|
|
6874
|
-
* Intentionally not a {@link AbstractWebGl2Renderer} subclass: this
|
|
6875
|
-
* compositor is invoked directly by the manager for non-Drawable
|
|
6876
|
-
* compositing operations and never participates in the renderer
|
|
6877
|
-
* registry dispatch path.
|
|
6878
|
-
*/
|
|
6879
|
-
class WebGl2MaskCompositor {
|
|
6880
|
-
_shader = new Shader(vertexSource, fragmentSource);
|
|
6881
|
-
_vertexData = new ArrayBuffer(4 * vertexStrideBytes$3);
|
|
6882
|
-
_float32View = new Float32Array(this._vertexData);
|
|
6883
|
-
_contentSamplerSlot = new Int32Array([0]);
|
|
6884
|
-
_maskSamplerSlot = new Int32Array([1]);
|
|
6885
|
-
_connection = null;
|
|
6886
|
-
connect(backend) {
|
|
6887
|
-
if (this._connection !== null) {
|
|
6888
|
-
return;
|
|
6889
|
-
}
|
|
6890
|
-
const gl = backend.context;
|
|
6891
|
-
const vaoHandle = gl.createVertexArray();
|
|
6892
|
-
if (vaoHandle === null) {
|
|
6893
|
-
throw new Error('WebGl2MaskCompositor: could not create vertex array object.');
|
|
6894
|
-
}
|
|
6895
|
-
this._shader.connect(createWebGl2ShaderProgram(gl));
|
|
6896
|
-
const bufferHandles = new Map();
|
|
6897
|
-
const indexBuffer = new WebGl2RenderBuffer(BufferTypes.ElementArrayBuffer, quadIndices$1, BufferUsage.StaticDraw)
|
|
6898
|
-
.connect(this._createBufferRuntime(gl, bufferHandles));
|
|
6899
|
-
const vertexBuffer = new WebGl2RenderBuffer(BufferTypes.ArrayBuffer, this._vertexData, BufferUsage.DynamicDraw)
|
|
6900
|
-
.connect(this._createBufferRuntime(gl, bufferHandles));
|
|
6901
|
-
// Force shader finalize so getAttribute() below sees a populated
|
|
6902
|
-
// attribute table; the async-compile path defers extraction until
|
|
6903
|
-
// the first sync() call.
|
|
6904
|
-
this._shader.sync();
|
|
6905
|
-
const vao = new WebGl2VertexArrayObject()
|
|
6906
|
-
.addIndex(indexBuffer)
|
|
6907
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_position'), gl.FLOAT, false, vertexStrideBytes$3, 0)
|
|
6908
|
-
.addAttribute(vertexBuffer, this._shader.getAttribute('a_texcoord'), gl.FLOAT, false, vertexStrideBytes$3, 8)
|
|
6909
|
-
.connect(this._createVaoRuntime(gl, vaoHandle));
|
|
6910
|
-
this._connection = { gl, vaoHandle, vao, indexBuffer, vertexBuffer, bufferHandles };
|
|
6911
|
-
}
|
|
6912
|
-
disconnect() {
|
|
6913
|
-
const connection = this._connection;
|
|
6914
|
-
if (connection === null) {
|
|
6976
|
+
if (connection === null) {
|
|
6915
6977
|
return;
|
|
6916
6978
|
}
|
|
6917
6979
|
connection.indexBuffer.destroy();
|
|
@@ -7197,116 +7259,6 @@ class Sprite extends Drawable {
|
|
|
7197
7259
|
}
|
|
7198
7260
|
RenderNode.setInternalSpriteFactory(() => new Sprite(null));
|
|
7199
7261
|
|
|
7200
|
-
/**
|
|
7201
|
-
* Arbitrary 2D triangle-mesh primitive.
|
|
7202
|
-
*
|
|
7203
|
-
* `Mesh` lives alongside {@link Sprite} as a public Drawable: it has the
|
|
7204
|
-
* same transform (position/rotation/scale/origin), tint, blendMode,
|
|
7205
|
-
* filters, masks, and cacheAsBitmap — but the geometry it renders is
|
|
7206
|
-
* user-supplied rather than implied by a texture frame. The intended use
|
|
7207
|
-
* cases are:
|
|
7208
|
-
*
|
|
7209
|
-
* - Custom-shape sprites whose silhouette isn't a quad (badges, speech
|
|
7210
|
-
* bubbles, region overlays).
|
|
7211
|
-
* - Deformable visuals (rope/ribbon, banner, water surface): mutate the
|
|
7212
|
-
* vertex array between frames and the GPU re-tessellates nothing —
|
|
7213
|
-
* only the transform changes per frame.
|
|
7214
|
-
* - Particles or trails with custom geometry per emitter.
|
|
7215
|
-
*
|
|
7216
|
-
* The mesh data is **immutable after construction** in v1: vertex /
|
|
7217
|
-
* index / UV / color arrays are exposed as readonly references. Mutate
|
|
7218
|
-
* the underlying typed arrays in-place if you need per-frame updates,
|
|
7219
|
-
* but the array lengths and topology cannot change. Texture is the only
|
|
7220
|
-
* post-construction mutable property.
|
|
7221
|
-
*
|
|
7222
|
-
* The vertex stream is a flat `Float32Array` of (x, y) pairs in local
|
|
7223
|
-
* space. The mesh's local bounds are computed once at construction from
|
|
7224
|
-
* the AABB of those vertices and used by the cull pass. Re-computing
|
|
7225
|
-
* after in-place mutation is the caller's responsibility (call
|
|
7226
|
-
* `recomputeLocalBounds()`).
|
|
7227
|
-
*/
|
|
7228
|
-
class Mesh extends Drawable {
|
|
7229
|
-
vertices;
|
|
7230
|
-
indices;
|
|
7231
|
-
uvs;
|
|
7232
|
-
colors;
|
|
7233
|
-
_texture;
|
|
7234
|
-
constructor(options) {
|
|
7235
|
-
super();
|
|
7236
|
-
const { vertices, indices = null, uvs = null, colors = null, texture = null } = options;
|
|
7237
|
-
if (vertices.length === 0 || vertices.length % 2 !== 0) {
|
|
7238
|
-
throw new Error(`Mesh vertices must be a non-empty flat array of (x,y) pairs (got length ${vertices.length}).`);
|
|
7239
|
-
}
|
|
7240
|
-
const vertexCount = vertices.length / 2;
|
|
7241
|
-
if (vertexCount < 3) {
|
|
7242
|
-
throw new Error(`Mesh requires at least 3 vertices (got ${vertexCount}).`);
|
|
7243
|
-
}
|
|
7244
|
-
if (uvs !== null && uvs.length !== vertices.length) {
|
|
7245
|
-
throw new Error(`Mesh uvs length ${uvs.length} must equal vertices length ${vertices.length}.`);
|
|
7246
|
-
}
|
|
7247
|
-
if (colors !== null && colors.length !== vertexCount) {
|
|
7248
|
-
throw new Error(`Mesh colors length ${colors.length} must equal vertex count ${vertexCount}.`);
|
|
7249
|
-
}
|
|
7250
|
-
if (indices !== null) {
|
|
7251
|
-
if (indices.length === 0 || indices.length % 3 !== 0) {
|
|
7252
|
-
throw new Error(`Mesh indices must be a non-empty multiple of 3 (got length ${indices.length}).`);
|
|
7253
|
-
}
|
|
7254
|
-
for (let i = 0; i < indices.length; i++) {
|
|
7255
|
-
if (indices[i] >= vertexCount) {
|
|
7256
|
-
throw new Error(`Mesh index ${indices[i]} at position ${i} is out of range for vertex count ${vertexCount}.`);
|
|
7257
|
-
}
|
|
7258
|
-
}
|
|
7259
|
-
}
|
|
7260
|
-
else if (vertexCount % 3 !== 0) {
|
|
7261
|
-
throw new Error(`Non-indexed Mesh requires a vertex count that is a multiple of 3 (got ${vertexCount}).`);
|
|
7262
|
-
}
|
|
7263
|
-
this.vertices = vertices;
|
|
7264
|
-
this.indices = indices;
|
|
7265
|
-
this.uvs = uvs;
|
|
7266
|
-
this.colors = colors;
|
|
7267
|
-
this._texture = texture;
|
|
7268
|
-
this.recomputeLocalBounds();
|
|
7269
|
-
}
|
|
7270
|
-
get vertexCount() {
|
|
7271
|
-
return this.vertices.length / 2;
|
|
7272
|
-
}
|
|
7273
|
-
get indexCount() {
|
|
7274
|
-
return this.indices?.length ?? this.vertexCount;
|
|
7275
|
-
}
|
|
7276
|
-
get texture() {
|
|
7277
|
-
return this._texture;
|
|
7278
|
-
}
|
|
7279
|
-
set texture(texture) {
|
|
7280
|
-
this._texture = texture;
|
|
7281
|
-
this.invalidateCache();
|
|
7282
|
-
}
|
|
7283
|
-
/**
|
|
7284
|
-
* Recompute the local AABB from the current vertex array. Call after
|
|
7285
|
-
* mutating `vertices` in place to keep culling correct; otherwise the
|
|
7286
|
-
* bounds the cull pass sees will be the AABB at construction time.
|
|
7287
|
-
*/
|
|
7288
|
-
recomputeLocalBounds() {
|
|
7289
|
-
let minX = Infinity;
|
|
7290
|
-
let minY = Infinity;
|
|
7291
|
-
let maxX = -Infinity;
|
|
7292
|
-
let maxY = -Infinity;
|
|
7293
|
-
for (let i = 0; i < this.vertices.length; i += 2) {
|
|
7294
|
-
const x = this.vertices[i];
|
|
7295
|
-
const y = this.vertices[i + 1];
|
|
7296
|
-
if (x < minX)
|
|
7297
|
-
minX = x;
|
|
7298
|
-
if (x > maxX)
|
|
7299
|
-
maxX = x;
|
|
7300
|
-
if (y < minY)
|
|
7301
|
-
minY = y;
|
|
7302
|
-
if (y > maxY)
|
|
7303
|
-
maxY = y;
|
|
7304
|
-
}
|
|
7305
|
-
this.localBounds.set(minX, minY, maxX - minX, maxY - minY);
|
|
7306
|
-
return this;
|
|
7307
|
-
}
|
|
7308
|
-
}
|
|
7309
|
-
|
|
7310
7262
|
class Particle {
|
|
7311
7263
|
_totalLifetime = Time.oneSecond.clone();
|
|
7312
7264
|
_elapsedLifetime = Time.zero.clone();
|
|
@@ -7749,7 +7701,7 @@ class WebGl2Backend {
|
|
|
7749
7701
|
_boundFramebuffer = null;
|
|
7750
7702
|
_stats = createRenderStats();
|
|
7751
7703
|
constructor(app) {
|
|
7752
|
-
const { width, height, clearColor, webglAttributes, debug, spriteRendererBatchSize, particleRendererBatchSize,
|
|
7704
|
+
const { width, height, clearColor, webglAttributes, debug, spriteRendererBatchSize, particleRendererBatchSize, } = app.options;
|
|
7753
7705
|
this._canvas = app.canvas;
|
|
7754
7706
|
const gl = this._createContext(webglAttributes);
|
|
7755
7707
|
if (!gl) {
|
|
@@ -7773,7 +7725,6 @@ class WebGl2Backend {
|
|
|
7773
7725
|
this.rendererRegistry.registerRenderer(Sprite, new WebGl2SpriteRenderer(spriteRendererBatchSize));
|
|
7774
7726
|
this.rendererRegistry.registerRenderer(Mesh, new WebGl2MeshRenderer());
|
|
7775
7727
|
this.rendererRegistry.registerRenderer(ParticleSystem, new WebGl2ParticleRenderer(particleRendererBatchSize));
|
|
7776
|
-
this.rendererRegistry.registerRenderer(DrawableShape, new WebGl2PrimitiveRenderer(primitiveRendererBatchSize));
|
|
7777
7728
|
this.rendererRegistry.connect(this);
|
|
7778
7729
|
this._bindRenderTarget(this._renderTarget);
|
|
7779
7730
|
this.setBlendMode(BlendModes.Normal);
|
|
@@ -8246,661 +8197,180 @@ class WebGl2Backend {
|
|
|
8246
8197
|
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, texture.source);
|
|
8247
8198
|
}
|
|
8248
8199
|
}
|
|
8249
|
-
if (texture.generateMipMap && (texture instanceof RenderTexture || texture.source !== null)) {
|
|
8250
|
-
gl.generateMipmap(gl.TEXTURE_2D);
|
|
8251
|
-
}
|
|
8252
|
-
state.version = version;
|
|
8253
|
-
state.width = texture.width;
|
|
8254
|
-
state.height = texture.height;
|
|
8255
|
-
}
|
|
8256
|
-
return state;
|
|
8257
|
-
}
|
|
8258
|
-
_toClipPixels(bounds) {
|
|
8259
|
-
const topLeft = this._renderTarget.mapCoordsToPixel(this._clipPointA.set(bounds.left, bounds.top));
|
|
8260
|
-
const bottomRight = this._renderTarget.mapCoordsToPixel(this._clipPointB.set(bounds.right, bounds.bottom));
|
|
8261
|
-
const minX = Math.min(topLeft.x, bottomRight.x);
|
|
8262
|
-
const maxX = Math.max(topLeft.x, bottomRight.x);
|
|
8263
|
-
const minY = Math.min(topLeft.y, bottomRight.y);
|
|
8264
|
-
const maxY = Math.max(topLeft.y, bottomRight.y);
|
|
8265
|
-
const targetWidth = this._renderTarget.width;
|
|
8266
|
-
const targetHeight = this._renderTarget.height;
|
|
8267
|
-
const x = Math.max(0, Math.min(targetWidth, Math.floor(minX)));
|
|
8268
|
-
const right = Math.max(0, Math.min(targetWidth, Math.ceil(maxX)));
|
|
8269
|
-
const yTop = Math.max(0, Math.min(targetHeight, Math.floor(minY)));
|
|
8270
|
-
const bottom = Math.max(0, Math.min(targetHeight, Math.ceil(maxY)));
|
|
8271
|
-
const width = Math.max(0, right - x);
|
|
8272
|
-
const height = Math.max(0, bottom - yTop);
|
|
8273
|
-
const y = Math.max(0, targetHeight - bottom);
|
|
8274
|
-
return {
|
|
8275
|
-
x,
|
|
8276
|
-
y,
|
|
8277
|
-
width,
|
|
8278
|
-
height,
|
|
8279
|
-
};
|
|
8280
|
-
}
|
|
8281
|
-
_intersectClips(first, second) {
|
|
8282
|
-
const left = Math.max(first.x, second.x);
|
|
8283
|
-
const bottom = Math.max(first.y, second.y);
|
|
8284
|
-
const right = Math.min(first.x + first.width, second.x + second.width);
|
|
8285
|
-
const top = Math.min(first.y + first.height, second.y + second.height);
|
|
8286
|
-
return {
|
|
8287
|
-
x: left,
|
|
8288
|
-
y: bottom,
|
|
8289
|
-
width: Math.max(0, right - left),
|
|
8290
|
-
height: Math.max(0, top - bottom),
|
|
8291
|
-
};
|
|
8292
|
-
}
|
|
8293
|
-
_applyClipState() {
|
|
8294
|
-
const gl = this._context;
|
|
8295
|
-
if (this._clipPixelStack.length === 0) {
|
|
8296
|
-
gl.disable(gl.SCISSOR_TEST);
|
|
8297
|
-
return;
|
|
8298
|
-
}
|
|
8299
|
-
const clip = this._clipPixelStack[this._clipPixelStack.length - 1];
|
|
8300
|
-
gl.enable(gl.SCISSOR_TEST);
|
|
8301
|
-
gl.scissor(clip.x, clip.y, clip.width, clip.height);
|
|
8302
|
-
}
|
|
8303
|
-
}
|
|
8304
|
-
|
|
8305
|
-
/// <reference types="@webgpu/types" />
|
|
8306
|
-
/**
|
|
8307
|
-
* Base class for WebGPU renderers.
|
|
8308
|
-
*
|
|
8309
|
-
* Manages the connect/disconnect lifecycle and provides a safe
|
|
8310
|
-
* `getBackend()` accessor that throws if the renderer is not connected.
|
|
8311
|
-
*
|
|
8312
|
-
* Subclasses must implement:
|
|
8313
|
-
* - onConnect(backend): set up GPU resources (shader modules, pipelines, buffers)
|
|
8314
|
-
* - onDisconnect(): tear down GPU resources
|
|
8315
|
-
* - render(drawable): collect draw call data for the given drawable
|
|
8316
|
-
* - flush(): encode and submit command buffers for all collected draw calls
|
|
8317
|
-
*/
|
|
8318
|
-
class AbstractWebGpuRenderer {
|
|
8319
|
-
backendType = RenderBackendType.WebGpu;
|
|
8320
|
-
_backend = null;
|
|
8321
|
-
connect(backend) {
|
|
8322
|
-
if (this._backend !== null) {
|
|
8323
|
-
return;
|
|
8324
|
-
}
|
|
8325
|
-
if (backend.backendType !== RenderBackendType.WebGpu) {
|
|
8326
|
-
throw new Error(`${this.constructor.name} requires a WebGPU backend, `
|
|
8327
|
-
+ `but received backendType ${String(backend.backendType)}.`);
|
|
8328
|
-
}
|
|
8329
|
-
this._backend = backend;
|
|
8330
|
-
this.onConnect(backend);
|
|
8331
|
-
}
|
|
8332
|
-
disconnect() {
|
|
8333
|
-
if (this._backend === null) {
|
|
8334
|
-
return;
|
|
8335
|
-
}
|
|
8336
|
-
this.flush();
|
|
8337
|
-
this.onDisconnect();
|
|
8338
|
-
this._backend = null;
|
|
8339
|
-
}
|
|
8340
|
-
getBackend() {
|
|
8341
|
-
if (this._backend === null) {
|
|
8342
|
-
throw new Error(`${this.constructor.name} is not connected to a backend.`);
|
|
8343
|
-
}
|
|
8344
|
-
return this._backend;
|
|
8345
|
-
}
|
|
8346
|
-
getBackendOrNull() {
|
|
8347
|
-
return this._backend;
|
|
8348
|
-
}
|
|
8349
|
-
}
|
|
8350
|
-
|
|
8351
|
-
/// <reference types="@webgpu/types" />
|
|
8352
|
-
/**
|
|
8353
|
-
* Returns the GPUBlendState for a given ExoJS blend mode.
|
|
8354
|
-
* Shared by all WebGPU renderers to avoid duplication.
|
|
8355
|
-
*/
|
|
8356
|
-
function getWebGpuBlendState(blendMode) {
|
|
8357
|
-
switch (blendMode) {
|
|
8358
|
-
case BlendModes.Additive:
|
|
8359
|
-
return {
|
|
8360
|
-
color: {
|
|
8361
|
-
operation: 'add',
|
|
8362
|
-
srcFactor: 'one',
|
|
8363
|
-
dstFactor: 'one',
|
|
8364
|
-
},
|
|
8365
|
-
alpha: {
|
|
8366
|
-
operation: 'add',
|
|
8367
|
-
srcFactor: 'one',
|
|
8368
|
-
dstFactor: 'one',
|
|
8369
|
-
},
|
|
8370
|
-
};
|
|
8371
|
-
case BlendModes.Subtract:
|
|
8372
|
-
return {
|
|
8373
|
-
color: {
|
|
8374
|
-
operation: 'add',
|
|
8375
|
-
srcFactor: 'zero',
|
|
8376
|
-
dstFactor: 'one-minus-src',
|
|
8377
|
-
},
|
|
8378
|
-
alpha: {
|
|
8379
|
-
operation: 'add',
|
|
8380
|
-
srcFactor: 'zero',
|
|
8381
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8382
|
-
},
|
|
8383
|
-
};
|
|
8384
|
-
case BlendModes.Multiply:
|
|
8385
|
-
return {
|
|
8386
|
-
color: {
|
|
8387
|
-
operation: 'add',
|
|
8388
|
-
srcFactor: 'dst',
|
|
8389
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8390
|
-
},
|
|
8391
|
-
alpha: {
|
|
8392
|
-
operation: 'add',
|
|
8393
|
-
srcFactor: 'dst-alpha',
|
|
8394
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8395
|
-
},
|
|
8396
|
-
};
|
|
8397
|
-
case BlendModes.Screen:
|
|
8398
|
-
return {
|
|
8399
|
-
color: {
|
|
8400
|
-
operation: 'add',
|
|
8401
|
-
srcFactor: 'one',
|
|
8402
|
-
dstFactor: 'one-minus-src',
|
|
8403
|
-
},
|
|
8404
|
-
alpha: {
|
|
8405
|
-
operation: 'add',
|
|
8406
|
-
srcFactor: 'one',
|
|
8407
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8408
|
-
},
|
|
8409
|
-
};
|
|
8410
|
-
default:
|
|
8411
|
-
return {
|
|
8412
|
-
color: {
|
|
8413
|
-
operation: 'add',
|
|
8414
|
-
srcFactor: 'one',
|
|
8415
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8416
|
-
},
|
|
8417
|
-
alpha: {
|
|
8418
|
-
operation: 'add',
|
|
8419
|
-
srcFactor: 'one',
|
|
8420
|
-
dstFactor: 'one-minus-src-alpha',
|
|
8421
|
-
},
|
|
8422
|
-
};
|
|
8423
|
-
}
|
|
8424
|
-
}
|
|
8425
|
-
|
|
8426
|
-
/// <reference types="@webgpu/types" />
|
|
8427
|
-
const primitiveShaderSource = `
|
|
8428
|
-
struct VertexInput {
|
|
8429
|
-
@location(0) position: vec4<f32>,
|
|
8430
|
-
@location(1) color: vec4<f32>,
|
|
8431
|
-
};
|
|
8432
|
-
|
|
8433
|
-
struct VertexOutput {
|
|
8434
|
-
@builtin(position) position: vec4<f32>,
|
|
8435
|
-
@location(0) color: vec4<f32>,
|
|
8436
|
-
};
|
|
8437
|
-
|
|
8438
|
-
@vertex
|
|
8439
|
-
fn vertexMain(input: VertexInput) -> VertexOutput {
|
|
8440
|
-
var output: VertexOutput;
|
|
8441
|
-
|
|
8442
|
-
output.position = input.position;
|
|
8443
|
-
output.color = vec4<f32>(input.color.rgb * input.color.a, input.color.a);
|
|
8444
|
-
|
|
8445
|
-
return output;
|
|
8446
|
-
}
|
|
8447
|
-
|
|
8448
|
-
@fragment
|
|
8449
|
-
fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
8450
|
-
return input.color;
|
|
8451
|
-
}
|
|
8452
|
-
`;
|
|
8453
|
-
// 4 floats (pre-transformed clip-space position) + 1 u32 (color) = 20 bytes.
|
|
8454
|
-
// The CPU applies (view * shape.globalTransform) to each vertex before writing
|
|
8455
|
-
// it into the vertex buffer, so the shader outputs the position as-is. This
|
|
8456
|
-
// matches the sprite renderer's approach and eliminates the need for a per-
|
|
8457
|
-
// drawcall uniform binding.
|
|
8458
|
-
const vertexStrideBytes$2 = 20;
|
|
8459
|
-
const wordsPerVertex$1 = vertexStrideBytes$2 / Uint32Array.BYTES_PER_ELEMENT;
|
|
8460
|
-
class WebGpuPrimitiveRenderer extends AbstractWebGpuRenderer {
|
|
8461
|
-
_combinedTransform = new Matrix();
|
|
8462
|
-
_drawCalls = [];
|
|
8463
|
-
_drawCallCount = 0;
|
|
8464
|
-
_pipelines = new Map();
|
|
8465
|
-
_device = null;
|
|
8466
|
-
_shaderModule = null;
|
|
8467
|
-
_pipelineLayout = null;
|
|
8468
|
-
_vertexBuffer = null;
|
|
8469
|
-
_indexBuffer = null;
|
|
8470
|
-
_vertexBufferCapacity = 0;
|
|
8471
|
-
_indexBufferCapacity = 0;
|
|
8472
|
-
_vertexData = new ArrayBuffer(0);
|
|
8473
|
-
_float32View = new Float32Array(this._vertexData);
|
|
8474
|
-
_uint32View = new Uint32Array(this._vertexData);
|
|
8475
|
-
_packedIndexData = new Uint16Array(0);
|
|
8476
|
-
_generatedIndexData = new Uint16Array(0);
|
|
8477
|
-
_sequentialIndexData = new Uint16Array(0);
|
|
8478
|
-
render(shape) {
|
|
8479
|
-
const backend = this._backend;
|
|
8480
|
-
if (backend === null) {
|
|
8481
|
-
throw new Error('Renderer not connected');
|
|
8482
|
-
}
|
|
8483
|
-
if (shape.drawMode !== RenderingPrimitives.Points
|
|
8484
|
-
&& shape.drawMode !== RenderingPrimitives.Lines
|
|
8485
|
-
&& shape.drawMode !== RenderingPrimitives.LineLoop
|
|
8486
|
-
&& shape.drawMode !== RenderingPrimitives.LineStrip
|
|
8487
|
-
&& shape.drawMode !== RenderingPrimitives.Triangles
|
|
8488
|
-
&& shape.drawMode !== RenderingPrimitives.TriangleFan
|
|
8489
|
-
&& shape.drawMode !== RenderingPrimitives.TriangleStrip) {
|
|
8490
|
-
throw new Error(`WebGPU primitive renderer does not support draw mode "${shape.drawMode}" yet.`);
|
|
8491
|
-
}
|
|
8492
|
-
backend.setBlendMode(shape.blendMode);
|
|
8493
|
-
if (shape.geometry.vertices.length === 0) {
|
|
8494
|
-
return;
|
|
8495
|
-
}
|
|
8496
|
-
const drawCallIndex = this._drawCallCount++;
|
|
8497
|
-
const drawCall = this._drawCalls[drawCallIndex];
|
|
8498
|
-
if (drawCall) {
|
|
8499
|
-
drawCall.shape = shape;
|
|
8500
|
-
drawCall.blendMode = shape.blendMode;
|
|
8501
|
-
}
|
|
8502
|
-
else {
|
|
8503
|
-
this._drawCalls.push({
|
|
8504
|
-
shape,
|
|
8505
|
-
blendMode: shape.blendMode,
|
|
8506
|
-
});
|
|
8507
|
-
}
|
|
8508
|
-
}
|
|
8509
|
-
flush() {
|
|
8510
|
-
const backend = this._backend;
|
|
8511
|
-
const device = this._device;
|
|
8512
|
-
if (!backend || !device) {
|
|
8513
|
-
return;
|
|
8514
|
-
}
|
|
8515
|
-
if (this._drawCallCount === 0 && !backend.clearRequested) {
|
|
8516
|
-
return;
|
|
8517
|
-
}
|
|
8518
|
-
const scissor = backend.getScissorRect();
|
|
8519
|
-
const maskClipsAll = scissor !== null && (scissor.width <= 0 || scissor.height <= 0);
|
|
8520
|
-
// Phase 1: resolve drawcalls and record each one's offsets into the
|
|
8521
|
-
// shared packed buffers. Transform gets baked into the vertex data
|
|
8522
|
-
// during phase 2 so no per-drawcall uniform binding is needed.
|
|
8523
|
-
const plan = [];
|
|
8524
|
-
const resolvedDrawCalls = [];
|
|
8525
|
-
let totalVertices = 0;
|
|
8526
|
-
let totalIndices = 0;
|
|
8527
|
-
if (this._drawCallCount > 0 && !maskClipsAll) {
|
|
8528
|
-
for (let drawCallIndex = 0; drawCallIndex < this._drawCallCount; drawCallIndex++) {
|
|
8529
|
-
const drawCall = this._drawCalls[drawCallIndex];
|
|
8530
|
-
const shape = drawCall.shape;
|
|
8531
|
-
const resolved = this._resolveDrawCall(shape);
|
|
8532
|
-
resolvedDrawCalls.push(resolved);
|
|
8533
|
-
if (resolved === null) {
|
|
8534
|
-
continue;
|
|
8535
|
-
}
|
|
8536
|
-
const pipeline = this._getPipeline({
|
|
8537
|
-
topology: resolved.topology,
|
|
8538
|
-
usesStripIndex: resolved.usesStripIndex,
|
|
8539
|
-
blendMode: drawCall.blendMode,
|
|
8540
|
-
format: backend.renderTargetFormat,
|
|
8541
|
-
});
|
|
8542
|
-
plan.push({
|
|
8543
|
-
pipeline,
|
|
8544
|
-
vertexByteOffset: totalVertices * vertexStrideBytes$2,
|
|
8545
|
-
vertexCount: resolved.vertexCount,
|
|
8546
|
-
indexByteOffset: totalIndices * Uint16Array.BYTES_PER_ELEMENT,
|
|
8547
|
-
indexCount: resolved.indexCount,
|
|
8548
|
-
});
|
|
8549
|
-
totalVertices += resolved.vertexCount;
|
|
8550
|
-
totalIndices += resolved.indexCount;
|
|
8551
|
-
}
|
|
8552
|
-
}
|
|
8553
|
-
// If nothing will actually render, still honor a pending clear with
|
|
8554
|
-
// a single empty pass so createColorAttachment consumes the clear
|
|
8555
|
-
// state exactly once.
|
|
8556
|
-
if (plan.length === 0) {
|
|
8557
|
-
if (backend.clearRequested) {
|
|
8558
|
-
const encoder = device.createCommandEncoder();
|
|
8559
|
-
const pass = encoder.beginRenderPass({
|
|
8560
|
-
colorAttachments: [backend.createColorAttachment()],
|
|
8561
|
-
});
|
|
8562
|
-
backend.stats.renderPasses++;
|
|
8563
|
-
pass.end();
|
|
8564
|
-
backend.submit(encoder.finish());
|
|
8565
|
-
}
|
|
8566
|
-
this._drawCallCount = 0;
|
|
8567
|
-
return;
|
|
8568
|
-
}
|
|
8569
|
-
// Phase 2: size GPU buffers for the whole-frame totals, then pack
|
|
8570
|
-
// every drawcall's CPU-side data. _writeShapeVertices applies
|
|
8571
|
-
// (view * shape.globalTransform) per-vertex so the shader simply
|
|
8572
|
-
// outputs input.position unchanged.
|
|
8573
|
-
this._ensureVertexCapacity(totalVertices);
|
|
8574
|
-
if (totalIndices > 0) {
|
|
8575
|
-
this._ensureIndexCapacity(totalIndices);
|
|
8576
|
-
if (this._packedIndexData.length < totalIndices) {
|
|
8577
|
-
this._packedIndexData = new Uint16Array(Math.max(totalIndices, this._packedIndexData.length === 0 ? 1 : this._packedIndexData.length * 2));
|
|
8578
|
-
}
|
|
8579
|
-
}
|
|
8580
|
-
{
|
|
8581
|
-
let vOffset = 0;
|
|
8582
|
-
let iOffset = 0;
|
|
8583
|
-
for (let i = 0; i < this._drawCallCount; i++) {
|
|
8584
|
-
const resolved = resolvedDrawCalls[i];
|
|
8585
|
-
if (resolved === null) {
|
|
8586
|
-
continue;
|
|
8587
|
-
}
|
|
8588
|
-
const drawCall = this._drawCalls[i];
|
|
8589
|
-
const shape = drawCall.shape;
|
|
8590
|
-
this._writeShapeVertices(backend, shape, vOffset);
|
|
8591
|
-
if (resolved.indices !== null && resolved.indexCount > 0) {
|
|
8592
|
-
this._packedIndexData.set(resolved.indices.subarray(0, resolved.indexCount), iOffset);
|
|
8593
|
-
iOffset += resolved.indexCount;
|
|
8594
|
-
}
|
|
8595
|
-
vOffset += resolved.vertexCount;
|
|
8596
|
-
}
|
|
8597
|
-
}
|
|
8598
|
-
// Phase 3: single writeBuffer per GPU buffer covers the whole frame.
|
|
8599
|
-
device.queue.writeBuffer(this._vertexBuffer, 0, this._vertexData, 0, totalVertices * vertexStrideBytes$2);
|
|
8600
|
-
if (totalIndices > 0) {
|
|
8601
|
-
device.queue.writeBuffer(this._indexBuffer, 0, this._packedIndexData.buffer, this._packedIndexData.byteOffset, totalIndices * Uint16Array.BYTES_PER_ELEMENT);
|
|
8602
|
-
}
|
|
8603
|
-
// Phase 4: single render pass. Per-draw state is just pipeline and
|
|
8604
|
-
// vertex/index subrange offsets — the transform has already been
|
|
8605
|
-
// baked into the vertex data.
|
|
8606
|
-
const encoder = device.createCommandEncoder();
|
|
8607
|
-
const pass = encoder.beginRenderPass({
|
|
8608
|
-
colorAttachments: [backend.createColorAttachment()],
|
|
8609
|
-
});
|
|
8610
|
-
backend.stats.renderPasses++;
|
|
8611
|
-
if (scissor !== null) {
|
|
8612
|
-
pass.setScissorRect(scissor.x, scissor.y, scissor.width, scissor.height);
|
|
8613
|
-
}
|
|
8614
|
-
for (const planned of plan) {
|
|
8615
|
-
pass.setPipeline(planned.pipeline);
|
|
8616
|
-
pass.setVertexBuffer(0, this._vertexBuffer, planned.vertexByteOffset);
|
|
8617
|
-
if (planned.indexCount > 0) {
|
|
8618
|
-
pass.setIndexBuffer(this._indexBuffer, 'uint16', planned.indexByteOffset);
|
|
8619
|
-
pass.drawIndexed(planned.indexCount);
|
|
8620
|
-
}
|
|
8621
|
-
else {
|
|
8622
|
-
pass.draw(planned.vertexCount);
|
|
8623
|
-
}
|
|
8624
|
-
backend.stats.batches++;
|
|
8625
|
-
backend.stats.drawCalls++;
|
|
8626
|
-
}
|
|
8627
|
-
pass.end();
|
|
8628
|
-
backend.submit(encoder.finish());
|
|
8629
|
-
this._drawCallCount = 0;
|
|
8630
|
-
}
|
|
8631
|
-
destroy() {
|
|
8632
|
-
this.disconnect();
|
|
8633
|
-
this._combinedTransform.destroy();
|
|
8634
|
-
}
|
|
8635
|
-
onConnect(backend) {
|
|
8636
|
-
this._backend = backend;
|
|
8637
|
-
this._device = this._backend.device;
|
|
8638
|
-
this._shaderModule = this._device.createShaderModule({ code: primitiveShaderSource });
|
|
8639
|
-
// Transform is applied per-vertex on the CPU, so no uniform binding
|
|
8640
|
-
// is needed — the shader outputs input.position directly.
|
|
8641
|
-
this._pipelineLayout = this._device.createPipelineLayout({
|
|
8642
|
-
bindGroupLayouts: [],
|
|
8643
|
-
});
|
|
8644
|
-
}
|
|
8645
|
-
onDisconnect() {
|
|
8646
|
-
this.flush();
|
|
8647
|
-
this._destroyBuffers();
|
|
8648
|
-
this._pipelines.clear();
|
|
8649
|
-
this._pipelineLayout = null;
|
|
8650
|
-
this._shaderModule = null;
|
|
8651
|
-
this._device = null;
|
|
8652
|
-
this._backend = null;
|
|
8653
|
-
this._drawCallCount = 0;
|
|
8654
|
-
}
|
|
8655
|
-
_writeShapeVertices(backend, shape, vertexStart) {
|
|
8656
|
-
// Matrix.combine is `other * this` (see Matrix.rotate and
|
|
8657
|
-
// SceneNode.getGlobalTransform, both of which chain via
|
|
8658
|
-
// local.combine(parent.global) to yield parent.global * local).
|
|
8659
|
-
//
|
|
8660
|
-
// We need view * global applied to a local vertex, so start with
|
|
8661
|
-
// global and combine with view — that gives
|
|
8662
|
-
// _combinedTransform = view * global.
|
|
8663
|
-
const matrix = this._combinedTransform
|
|
8664
|
-
.copy(shape.getGlobalTransform())
|
|
8665
|
-
.combine(backend.view.getTransform());
|
|
8666
|
-
// Match the original uniform-based WGSL layout exactly.
|
|
8667
|
-
//
|
|
8668
|
-
// The shader packs the Matrix's 9 fields into a 4x4 mat (column-major
|
|
8669
|
-
// in WGSL):
|
|
8670
|
-
// col 0 = [a, c, 0, 0]
|
|
8671
|
-
// col 1 = [b, d, 0, 0]
|
|
8672
|
-
// col 2 = [0, 0, 1, 0]
|
|
8673
|
-
// col 3 = [x, y, 0, z]
|
|
8674
|
-
//
|
|
8675
|
-
// Multiplied by vec4(px, py, 0, 1):
|
|
8676
|
-
// out = col0*px + col1*py + col2*0 + col3*1
|
|
8677
|
-
// out.x = a*px + b*py + x
|
|
8678
|
-
// out.y = c*px + d*py + y
|
|
8679
|
-
// out.z = 0
|
|
8680
|
-
// out.w = z
|
|
8681
|
-
//
|
|
8682
|
-
// The Matrix class represents the affine matrix in the order
|
|
8683
|
-
// [a b x]
|
|
8684
|
-
// [c d y]
|
|
8685
|
-
// [e f z]
|
|
8686
|
-
// so a/b/c/d are rotation+scale (note: b on the TOP row, c on the
|
|
8687
|
-
// LEFT column, not the other way around) and x/y/z the translation /
|
|
8688
|
-
// w component. Matrix.toArray(false) confirms this layout.
|
|
8689
|
-
const a = matrix.a;
|
|
8690
|
-
const b = matrix.b;
|
|
8691
|
-
const c = matrix.c;
|
|
8692
|
-
const d = matrix.d;
|
|
8693
|
-
const tx = matrix.x;
|
|
8694
|
-
const ty = matrix.y;
|
|
8695
|
-
const tw = matrix.z;
|
|
8696
|
-
const color = shape.color.toRgba();
|
|
8697
|
-
const vertices = shape.geometry.vertices;
|
|
8698
|
-
const vertexCount = vertices.length / 2;
|
|
8699
|
-
for (let i = 0; i < vertexCount; i++) {
|
|
8700
|
-
const sourceIndex = i * 2;
|
|
8701
|
-
const targetIndex = (vertexStart + i) * wordsPerVertex$1;
|
|
8702
|
-
const px = vertices[sourceIndex];
|
|
8703
|
-
const py = vertices[sourceIndex + 1];
|
|
8704
|
-
this._float32View[targetIndex + 0] = a * px + b * py + tx;
|
|
8705
|
-
this._float32View[targetIndex + 1] = c * px + d * py + ty;
|
|
8706
|
-
this._float32View[targetIndex + 2] = 0;
|
|
8707
|
-
this._float32View[targetIndex + 3] = tw;
|
|
8708
|
-
this._uint32View[targetIndex + 4] = color;
|
|
8709
|
-
}
|
|
8710
|
-
}
|
|
8711
|
-
_ensureVertexCapacity(vertexCount) {
|
|
8712
|
-
const requiredBytes = vertexCount * vertexStrideBytes$2;
|
|
8713
|
-
if (requiredBytes > this._vertexData.byteLength) {
|
|
8714
|
-
const byteLength = Math.max(requiredBytes, this._vertexData.byteLength === 0 ? vertexStrideBytes$2 : this._vertexData.byteLength * 2);
|
|
8715
|
-
this._vertexData = new ArrayBuffer(byteLength);
|
|
8716
|
-
this._float32View = new Float32Array(this._vertexData);
|
|
8717
|
-
this._uint32View = new Uint32Array(this._vertexData);
|
|
8718
|
-
}
|
|
8719
|
-
if (requiredBytes > this._vertexBufferCapacity) {
|
|
8720
|
-
this._vertexBuffer?.destroy();
|
|
8721
|
-
this._vertexBufferCapacity = Math.max(requiredBytes, this._vertexBufferCapacity === 0 ? vertexStrideBytes$2 : this._vertexBufferCapacity * 2);
|
|
8722
|
-
this._vertexBuffer = this._device.createBuffer({
|
|
8723
|
-
size: this._vertexBufferCapacity,
|
|
8724
|
-
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
8725
|
-
});
|
|
8726
|
-
}
|
|
8727
|
-
}
|
|
8728
|
-
_ensureIndexCapacity(indexCount) {
|
|
8729
|
-
const requiredBytes = indexCount * Uint16Array.BYTES_PER_ELEMENT;
|
|
8730
|
-
if (requiredBytes > this._indexBufferCapacity) {
|
|
8731
|
-
this._indexBuffer?.destroy();
|
|
8732
|
-
this._indexBufferCapacity = Math.max(requiredBytes, this._indexBufferCapacity === 0 ? Uint16Array.BYTES_PER_ELEMENT : this._indexBufferCapacity * 2);
|
|
8733
|
-
this._indexBuffer = this._device.createBuffer({
|
|
8734
|
-
size: this._indexBufferCapacity,
|
|
8735
|
-
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
8736
|
-
});
|
|
8737
|
-
}
|
|
8738
|
-
}
|
|
8739
|
-
_getPipeline(key) {
|
|
8740
|
-
const pipelineKey = `${key.topology}:${key.usesStripIndex ? 1 : 0}:${key.blendMode}:${key.format}`;
|
|
8741
|
-
const existingPipeline = this._pipelines.get(pipelineKey);
|
|
8742
|
-
if (existingPipeline) {
|
|
8743
|
-
return existingPipeline;
|
|
8744
|
-
}
|
|
8745
|
-
const pipeline = this._device.createRenderPipeline({
|
|
8746
|
-
layout: this._pipelineLayout,
|
|
8747
|
-
vertex: {
|
|
8748
|
-
module: this._shaderModule,
|
|
8749
|
-
entryPoint: 'vertexMain',
|
|
8750
|
-
buffers: [{
|
|
8751
|
-
arrayStride: vertexStrideBytes$2,
|
|
8752
|
-
attributes: [{
|
|
8753
|
-
shaderLocation: 0,
|
|
8754
|
-
offset: 0,
|
|
8755
|
-
format: 'float32x4',
|
|
8756
|
-
}, {
|
|
8757
|
-
shaderLocation: 1,
|
|
8758
|
-
offset: 16,
|
|
8759
|
-
format: 'unorm8x4',
|
|
8760
|
-
}],
|
|
8761
|
-
}],
|
|
8762
|
-
},
|
|
8763
|
-
fragment: {
|
|
8764
|
-
module: this._shaderModule,
|
|
8765
|
-
entryPoint: 'fragmentMain',
|
|
8766
|
-
targets: [{
|
|
8767
|
-
format: key.format,
|
|
8768
|
-
blend: getWebGpuBlendState(key.blendMode),
|
|
8769
|
-
writeMask: GPUColorWrite.ALL,
|
|
8770
|
-
}],
|
|
8771
|
-
},
|
|
8772
|
-
primitive: {
|
|
8773
|
-
topology: key.topology,
|
|
8774
|
-
stripIndexFormat: key.usesStripIndex ? 'uint16' : undefined,
|
|
8775
|
-
},
|
|
8776
|
-
});
|
|
8777
|
-
this._pipelines.set(pipelineKey, pipeline);
|
|
8778
|
-
return pipeline;
|
|
8779
|
-
}
|
|
8780
|
-
_getTopology(drawMode) {
|
|
8781
|
-
switch (drawMode) {
|
|
8782
|
-
case RenderingPrimitives.Points:
|
|
8783
|
-
return 'point-list';
|
|
8784
|
-
case RenderingPrimitives.Lines:
|
|
8785
|
-
return 'line-list';
|
|
8786
|
-
case RenderingPrimitives.LineLoop:
|
|
8787
|
-
case RenderingPrimitives.LineStrip:
|
|
8788
|
-
return 'line-strip';
|
|
8789
|
-
case RenderingPrimitives.Triangles:
|
|
8790
|
-
case RenderingPrimitives.TriangleFan:
|
|
8791
|
-
return 'triangle-list';
|
|
8792
|
-
case RenderingPrimitives.TriangleStrip:
|
|
8793
|
-
return 'triangle-strip';
|
|
8794
|
-
default:
|
|
8795
|
-
throw new Error(`WebGPU primitive renderer does not support draw mode "${drawMode}" yet.`);
|
|
8796
|
-
}
|
|
8797
|
-
}
|
|
8798
|
-
_resolveDrawCall(shape) {
|
|
8799
|
-
const vertices = shape.geometry.vertices;
|
|
8800
|
-
const vertexCount = vertices.length / 2;
|
|
8801
|
-
if (vertexCount === 0) {
|
|
8802
|
-
return null;
|
|
8803
|
-
}
|
|
8804
|
-
switch (shape.drawMode) {
|
|
8805
|
-
case RenderingPrimitives.LineLoop:
|
|
8806
|
-
return this._resolveLineLoopDrawCall(shape.geometry.indices, vertexCount);
|
|
8807
|
-
case RenderingPrimitives.TriangleFan:
|
|
8808
|
-
return this._resolveTriangleFanDrawCall(shape.geometry.indices, vertexCount);
|
|
8809
|
-
default: {
|
|
8810
|
-
const indices = shape.geometry.indices;
|
|
8811
|
-
const topology = this._getTopology(shape.drawMode);
|
|
8812
|
-
const indexCount = indices.length;
|
|
8813
|
-
const usesStripIndex = indexCount > 0 && (shape.drawMode === RenderingPrimitives.LineStrip
|
|
8814
|
-
|| shape.drawMode === RenderingPrimitives.TriangleStrip);
|
|
8815
|
-
if (indexCount > 0) {
|
|
8816
|
-
return {
|
|
8817
|
-
topology,
|
|
8818
|
-
usesStripIndex,
|
|
8819
|
-
vertexCount,
|
|
8820
|
-
indices,
|
|
8821
|
-
indexCount,
|
|
8822
|
-
};
|
|
8823
|
-
}
|
|
8824
|
-
return {
|
|
8825
|
-
topology,
|
|
8826
|
-
usesStripIndex,
|
|
8827
|
-
vertexCount,
|
|
8828
|
-
indices: null,
|
|
8829
|
-
indexCount: 0,
|
|
8830
|
-
};
|
|
8200
|
+
if (texture.generateMipMap && (texture instanceof RenderTexture || texture.source !== null)) {
|
|
8201
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
8831
8202
|
}
|
|
8203
|
+
state.version = version;
|
|
8204
|
+
state.width = texture.width;
|
|
8205
|
+
state.height = texture.height;
|
|
8832
8206
|
}
|
|
8207
|
+
return state;
|
|
8833
8208
|
}
|
|
8834
|
-
|
|
8835
|
-
const
|
|
8836
|
-
const
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
const
|
|
8841
|
-
const
|
|
8842
|
-
|
|
8843
|
-
|
|
8209
|
+
_toClipPixels(bounds) {
|
|
8210
|
+
const topLeft = this._renderTarget.mapCoordsToPixel(this._clipPointA.set(bounds.left, bounds.top));
|
|
8211
|
+
const bottomRight = this._renderTarget.mapCoordsToPixel(this._clipPointB.set(bounds.right, bounds.bottom));
|
|
8212
|
+
const minX = Math.min(topLeft.x, bottomRight.x);
|
|
8213
|
+
const maxX = Math.max(topLeft.x, bottomRight.x);
|
|
8214
|
+
const minY = Math.min(topLeft.y, bottomRight.y);
|
|
8215
|
+
const maxY = Math.max(topLeft.y, bottomRight.y);
|
|
8216
|
+
const targetWidth = this._renderTarget.width;
|
|
8217
|
+
const targetHeight = this._renderTarget.height;
|
|
8218
|
+
const x = Math.max(0, Math.min(targetWidth, Math.floor(minX)));
|
|
8219
|
+
const right = Math.max(0, Math.min(targetWidth, Math.ceil(maxX)));
|
|
8220
|
+
const yTop = Math.max(0, Math.min(targetHeight, Math.floor(minY)));
|
|
8221
|
+
const bottom = Math.max(0, Math.min(targetHeight, Math.ceil(maxY)));
|
|
8222
|
+
const width = Math.max(0, right - x);
|
|
8223
|
+
const height = Math.max(0, bottom - yTop);
|
|
8224
|
+
const y = Math.max(0, targetHeight - bottom);
|
|
8844
8225
|
return {
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
indexCount: loopIndexCount,
|
|
8226
|
+
x,
|
|
8227
|
+
y,
|
|
8228
|
+
width,
|
|
8229
|
+
height,
|
|
8850
8230
|
};
|
|
8851
8231
|
}
|
|
8852
|
-
|
|
8853
|
-
const
|
|
8854
|
-
const
|
|
8855
|
-
|
|
8856
|
-
|
|
8857
|
-
}
|
|
8858
|
-
const indexCount = (sourceCount - 2) * 3;
|
|
8859
|
-
const generatedIndices = this._ensureGeneratedIndexCapacity(indexCount);
|
|
8860
|
-
let targetIndex = 0;
|
|
8861
|
-
for (let index = 1; index < sourceCount - 1; index++) {
|
|
8862
|
-
generatedIndices[targetIndex++] = sourceIndices[0];
|
|
8863
|
-
generatedIndices[targetIndex++] = sourceIndices[index];
|
|
8864
|
-
generatedIndices[targetIndex++] = sourceIndices[index + 1];
|
|
8865
|
-
}
|
|
8232
|
+
_intersectClips(first, second) {
|
|
8233
|
+
const left = Math.max(first.x, second.x);
|
|
8234
|
+
const bottom = Math.max(first.y, second.y);
|
|
8235
|
+
const right = Math.min(first.x + first.width, second.x + second.width);
|
|
8236
|
+
const top = Math.min(first.y + first.height, second.y + second.height);
|
|
8866
8237
|
return {
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
indexCount,
|
|
8238
|
+
x: left,
|
|
8239
|
+
y: bottom,
|
|
8240
|
+
width: Math.max(0, right - left),
|
|
8241
|
+
height: Math.max(0, top - bottom),
|
|
8872
8242
|
};
|
|
8873
8243
|
}
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8244
|
+
_applyClipState() {
|
|
8245
|
+
const gl = this._context;
|
|
8246
|
+
if (this._clipPixelStack.length === 0) {
|
|
8247
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
8248
|
+
return;
|
|
8249
|
+
}
|
|
8250
|
+
const clip = this._clipPixelStack[this._clipPixelStack.length - 1];
|
|
8251
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
8252
|
+
gl.scissor(clip.x, clip.y, clip.width, clip.height);
|
|
8253
|
+
}
|
|
8254
|
+
}
|
|
8255
|
+
|
|
8256
|
+
/// <reference types="@webgpu/types" />
|
|
8257
|
+
/**
|
|
8258
|
+
* Base class for WebGPU renderers.
|
|
8259
|
+
*
|
|
8260
|
+
* Manages the connect/disconnect lifecycle and provides a safe
|
|
8261
|
+
* `getBackend()` accessor that throws if the renderer is not connected.
|
|
8262
|
+
*
|
|
8263
|
+
* Subclasses must implement:
|
|
8264
|
+
* - onConnect(backend): set up GPU resources (shader modules, pipelines, buffers)
|
|
8265
|
+
* - onDisconnect(): tear down GPU resources
|
|
8266
|
+
* - render(drawable): collect draw call data for the given drawable
|
|
8267
|
+
* - flush(): encode and submit command buffers for all collected draw calls
|
|
8268
|
+
*/
|
|
8269
|
+
class AbstractWebGpuRenderer {
|
|
8270
|
+
backendType = RenderBackendType.WebGpu;
|
|
8271
|
+
_backend = null;
|
|
8272
|
+
connect(backend) {
|
|
8273
|
+
if (this._backend !== null) {
|
|
8274
|
+
return;
|
|
8881
8275
|
}
|
|
8882
|
-
|
|
8883
|
-
this.
|
|
8276
|
+
if (backend.backendType !== RenderBackendType.WebGpu) {
|
|
8277
|
+
throw new Error(`${this.constructor.name} requires a WebGPU backend, `
|
|
8278
|
+
+ `but received backendType ${String(backend.backendType)}.`);
|
|
8884
8279
|
}
|
|
8885
|
-
|
|
8280
|
+
this._backend = backend;
|
|
8281
|
+
this.onConnect(backend);
|
|
8886
8282
|
}
|
|
8887
|
-
|
|
8888
|
-
if (
|
|
8889
|
-
|
|
8890
|
-
while (nextLength < indexCount) {
|
|
8891
|
-
nextLength *= 2;
|
|
8892
|
-
}
|
|
8893
|
-
this._generatedIndexData = new Uint16Array(nextLength);
|
|
8283
|
+
disconnect() {
|
|
8284
|
+
if (this._backend === null) {
|
|
8285
|
+
return;
|
|
8894
8286
|
}
|
|
8895
|
-
|
|
8287
|
+
this.flush();
|
|
8288
|
+
this.onDisconnect();
|
|
8289
|
+
this._backend = null;
|
|
8896
8290
|
}
|
|
8897
|
-
|
|
8898
|
-
this.
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
this.
|
|
8902
|
-
|
|
8903
|
-
|
|
8291
|
+
getBackend() {
|
|
8292
|
+
if (this._backend === null) {
|
|
8293
|
+
throw new Error(`${this.constructor.name} is not connected to a backend.`);
|
|
8294
|
+
}
|
|
8295
|
+
return this._backend;
|
|
8296
|
+
}
|
|
8297
|
+
getBackendOrNull() {
|
|
8298
|
+
return this._backend;
|
|
8299
|
+
}
|
|
8300
|
+
}
|
|
8301
|
+
|
|
8302
|
+
/// <reference types="@webgpu/types" />
|
|
8303
|
+
/**
|
|
8304
|
+
* Returns the GPUBlendState for a given ExoJS blend mode.
|
|
8305
|
+
* Shared by all WebGPU renderers to avoid duplication.
|
|
8306
|
+
*/
|
|
8307
|
+
function getWebGpuBlendState(blendMode) {
|
|
8308
|
+
switch (blendMode) {
|
|
8309
|
+
case BlendModes.Additive:
|
|
8310
|
+
return {
|
|
8311
|
+
color: {
|
|
8312
|
+
operation: 'add',
|
|
8313
|
+
srcFactor: 'one',
|
|
8314
|
+
dstFactor: 'one',
|
|
8315
|
+
},
|
|
8316
|
+
alpha: {
|
|
8317
|
+
operation: 'add',
|
|
8318
|
+
srcFactor: 'one',
|
|
8319
|
+
dstFactor: 'one',
|
|
8320
|
+
},
|
|
8321
|
+
};
|
|
8322
|
+
case BlendModes.Subtract:
|
|
8323
|
+
return {
|
|
8324
|
+
color: {
|
|
8325
|
+
operation: 'add',
|
|
8326
|
+
srcFactor: 'zero',
|
|
8327
|
+
dstFactor: 'one-minus-src',
|
|
8328
|
+
},
|
|
8329
|
+
alpha: {
|
|
8330
|
+
operation: 'add',
|
|
8331
|
+
srcFactor: 'zero',
|
|
8332
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8333
|
+
},
|
|
8334
|
+
};
|
|
8335
|
+
case BlendModes.Multiply:
|
|
8336
|
+
return {
|
|
8337
|
+
color: {
|
|
8338
|
+
operation: 'add',
|
|
8339
|
+
srcFactor: 'dst',
|
|
8340
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8341
|
+
},
|
|
8342
|
+
alpha: {
|
|
8343
|
+
operation: 'add',
|
|
8344
|
+
srcFactor: 'dst-alpha',
|
|
8345
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8346
|
+
},
|
|
8347
|
+
};
|
|
8348
|
+
case BlendModes.Screen:
|
|
8349
|
+
return {
|
|
8350
|
+
color: {
|
|
8351
|
+
operation: 'add',
|
|
8352
|
+
srcFactor: 'one',
|
|
8353
|
+
dstFactor: 'one-minus-src',
|
|
8354
|
+
},
|
|
8355
|
+
alpha: {
|
|
8356
|
+
operation: 'add',
|
|
8357
|
+
srcFactor: 'one',
|
|
8358
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8359
|
+
},
|
|
8360
|
+
};
|
|
8361
|
+
default:
|
|
8362
|
+
return {
|
|
8363
|
+
color: {
|
|
8364
|
+
operation: 'add',
|
|
8365
|
+
srcFactor: 'one',
|
|
8366
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8367
|
+
},
|
|
8368
|
+
alpha: {
|
|
8369
|
+
operation: 'add',
|
|
8370
|
+
srcFactor: 'one',
|
|
8371
|
+
dstFactor: 'one-minus-src-alpha',
|
|
8372
|
+
},
|
|
8373
|
+
};
|
|
8904
8374
|
}
|
|
8905
8375
|
}
|
|
8906
8376
|
|
|
@@ -10550,8 +10020,7 @@ class WebGpuMaskCompositor {
|
|
|
10550
10020
|
}
|
|
10551
10021
|
_writeProjectionMatrix(viewMatrix) {
|
|
10552
10022
|
// Pack the 3x3 affine view matrix into a 4x4 column-major mat4x4
|
|
10553
|
-
// for WGSL
|
|
10554
|
-
// (see `_writeShapeVertices` in that file for the rationale).
|
|
10023
|
+
// for WGSL.
|
|
10555
10024
|
const m = this._projectionMatrix.copy(viewMatrix);
|
|
10556
10025
|
const data = this._projectionData;
|
|
10557
10026
|
// col 0
|
|
@@ -10618,7 +10087,6 @@ class WebGpuBackend {
|
|
|
10618
10087
|
if (clearColor) {
|
|
10619
10088
|
this._clearColor.copy(clearColor);
|
|
10620
10089
|
}
|
|
10621
|
-
this.rendererRegistry.registerRenderer(DrawableShape, new WebGpuPrimitiveRenderer());
|
|
10622
10090
|
this.rendererRegistry.registerRenderer(Sprite, new WebGpuSpriteRenderer());
|
|
10623
10091
|
this.rendererRegistry.registerRenderer(Mesh, new WebGpuMeshRenderer());
|
|
10624
10092
|
this.rendererRegistry.registerRenderer(ParticleSystem, new WebGpuParticleRenderer());
|
|
@@ -14619,7 +14087,6 @@ const defaultAppSettings = {
|
|
|
14619
14087
|
debug: false,
|
|
14620
14088
|
spriteRendererBatchSize: 4096, // ~ 262kb
|
|
14621
14089
|
particleRendererBatchSize: 8192, // ~ 1.18mb
|
|
14622
|
-
primitiveRendererBatchSize: 65536, // ~ 786kb
|
|
14623
14090
|
gamepadDefinitions: [],
|
|
14624
14091
|
pointerDistanceThreshold: 10,
|
|
14625
14092
|
webglAttributes: {
|
|
@@ -14655,6 +14122,7 @@ class Application {
|
|
|
14655
14122
|
_frameRequest = 0;
|
|
14656
14123
|
_backendType;
|
|
14657
14124
|
_backend;
|
|
14125
|
+
_capabilities = null;
|
|
14658
14126
|
constructor(appSettings) {
|
|
14659
14127
|
this.options = {
|
|
14660
14128
|
canvas: appSettings?.canvas ?? createDefaultCanvas(),
|
|
@@ -14696,11 +14164,26 @@ class Application {
|
|
|
14696
14164
|
get backend() {
|
|
14697
14165
|
return this._backend;
|
|
14698
14166
|
}
|
|
14167
|
+
/**
|
|
14168
|
+
* Resolved capabilities for the host browser. Available after
|
|
14169
|
+
* {@link Application.start} resolves; reading before that throws.
|
|
14170
|
+
* For pre-start access use {@link Capabilities.ready} directly.
|
|
14171
|
+
*/
|
|
14172
|
+
get capabilities() {
|
|
14173
|
+
if (this._capabilities === null) {
|
|
14174
|
+
throw new Error('Application.capabilities is unavailable before start() resolves. Use `await Capabilities.ready` for pre-start checks.');
|
|
14175
|
+
}
|
|
14176
|
+
return this._capabilities;
|
|
14177
|
+
}
|
|
14699
14178
|
async start(scene) {
|
|
14700
14179
|
if (this._status === ApplicationStatus.Stopped) {
|
|
14701
14180
|
this._status = ApplicationStatus.Loading;
|
|
14181
|
+
// Kick off capability detection in parallel with renderer init —
|
|
14182
|
+
// both are mostly-async startup work, no point serializing them.
|
|
14183
|
+
const capabilitiesPromise = Capabilities.ready;
|
|
14702
14184
|
try {
|
|
14703
14185
|
await this.initializeRenderManager();
|
|
14186
|
+
this._capabilities = await capabilitiesPromise;
|
|
14704
14187
|
await this.sceneManager.setScene(scene);
|
|
14705
14188
|
this._frameRequest = requestAnimationFrame(this._updateHandler);
|
|
14706
14189
|
this._frameClock.restart();
|
|
@@ -14798,100 +14281,6 @@ class Application {
|
|
|
14798
14281
|
}
|
|
14799
14282
|
}
|
|
14800
14283
|
|
|
14801
|
-
// Browser feature-detection probes evaluated once at module load. The
|
|
14802
|
-
// resulting `capabilities` object is a `Readonly<Record<CapabilityName,
|
|
14803
|
-
// boolean>>` and can be inspected directly or queried via `isSupported`.
|
|
14804
|
-
//
|
|
14805
|
-
// All probes are synchronous. Async questions ("can I actually acquire a
|
|
14806
|
-
// WebGPU adapter?", "can the audio decoder play this OGG file?") are
|
|
14807
|
-
// outside this module's scope — they're owned by the Application's
|
|
14808
|
-
// backend selection and the Loader respectively. `capabilities.webgpu`
|
|
14809
|
-
// returning `true` only guarantees that the browser advertises WebGPU,
|
|
14810
|
-
// not that an adapter request will succeed.
|
|
14811
|
-
const hasWindow = typeof window !== 'undefined';
|
|
14812
|
-
const hasDocument = typeof document !== 'undefined';
|
|
14813
|
-
const hasNavigator = typeof navigator !== 'undefined';
|
|
14814
|
-
const probeWebGl2 = () => {
|
|
14815
|
-
if (!hasDocument)
|
|
14816
|
-
return false;
|
|
14817
|
-
try {
|
|
14818
|
-
const canvas = document.createElement('canvas');
|
|
14819
|
-
const gl = canvas.getContext('webgl2');
|
|
14820
|
-
return gl !== null;
|
|
14821
|
-
}
|
|
14822
|
-
catch {
|
|
14823
|
-
return false;
|
|
14824
|
-
}
|
|
14825
|
-
};
|
|
14826
|
-
const probeWebGpu = () => {
|
|
14827
|
-
return hasNavigator && 'gpu' in navigator;
|
|
14828
|
-
};
|
|
14829
|
-
const probeAudio = () => {
|
|
14830
|
-
if (!hasWindow)
|
|
14831
|
-
return false;
|
|
14832
|
-
const w = window;
|
|
14833
|
-
return typeof w.AudioContext !== 'undefined' || typeof w.webkitAudioContext !== 'undefined';
|
|
14834
|
-
};
|
|
14835
|
-
const probePointer = () => {
|
|
14836
|
-
return hasWindow && 'PointerEvent' in window;
|
|
14837
|
-
};
|
|
14838
|
-
const probeTouch = () => {
|
|
14839
|
-
if (!hasWindow)
|
|
14840
|
-
return false;
|
|
14841
|
-
if ('ontouchstart' in window)
|
|
14842
|
-
return true;
|
|
14843
|
-
if (hasNavigator && typeof navigator.maxTouchPoints === 'number' && navigator.maxTouchPoints > 0)
|
|
14844
|
-
return true;
|
|
14845
|
-
return false;
|
|
14846
|
-
};
|
|
14847
|
-
const probeGamepad = () => {
|
|
14848
|
-
return hasNavigator && typeof navigator.getGamepads === 'function';
|
|
14849
|
-
};
|
|
14850
|
-
const probeKeyboard = () => {
|
|
14851
|
-
return hasWindow && 'KeyboardEvent' in window;
|
|
14852
|
-
};
|
|
14853
|
-
const probeFullscreen = () => {
|
|
14854
|
-
if (!hasDocument)
|
|
14855
|
-
return false;
|
|
14856
|
-
const el = document.documentElement;
|
|
14857
|
-
return typeof el.requestFullscreen === 'function' || typeof el.webkitRequestFullscreen === 'function';
|
|
14858
|
-
};
|
|
14859
|
-
const probeVibration = () => {
|
|
14860
|
-
return hasNavigator && typeof navigator.vibrate === 'function';
|
|
14861
|
-
};
|
|
14862
|
-
const probeOffscreenCanvas = () => {
|
|
14863
|
-
// The browser global is verbatim `OffscreenCanvas`; eslint's
|
|
14864
|
-
// strict-camelCase rule rejects the property name even though we
|
|
14865
|
-
// can't rename a web standard.
|
|
14866
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
14867
|
-
return hasWindow && typeof window.OffscreenCanvas !== 'undefined';
|
|
14868
|
-
};
|
|
14869
|
-
/**
|
|
14870
|
-
* Synchronous, one-shot feature-detection results. Computed once at
|
|
14871
|
-
* module load and frozen. Use either as a property bag (`capabilities.touch`)
|
|
14872
|
-
* or via {@link isSupported} for typed lookup.
|
|
14873
|
-
*/
|
|
14874
|
-
const capabilities = Object.freeze({
|
|
14875
|
-
webgl2: probeWebGl2(),
|
|
14876
|
-
webgpu: probeWebGpu(),
|
|
14877
|
-
audio: probeAudio(),
|
|
14878
|
-
pointer: probePointer(),
|
|
14879
|
-
touch: probeTouch(),
|
|
14880
|
-
gamepad: probeGamepad(),
|
|
14881
|
-
keyboard: probeKeyboard(),
|
|
14882
|
-
fullscreen: probeFullscreen(),
|
|
14883
|
-
vibration: probeVibration(),
|
|
14884
|
-
offscreenCanvas: probeOffscreenCanvas(),
|
|
14885
|
-
});
|
|
14886
|
-
/**
|
|
14887
|
-
* Typed lookup over {@link capabilities}. Identical to
|
|
14888
|
-
* `capabilities[name]` but the function form gives clearer call-sites
|
|
14889
|
-
* when the name is computed.
|
|
14890
|
-
*/
|
|
14891
|
-
function isSupported(name) {
|
|
14892
|
-
return capabilities[name];
|
|
14893
|
-
}
|
|
14894
|
-
|
|
14895
14284
|
class Quadtree {
|
|
14896
14285
|
static maxSceneNodes = 50;
|
|
14897
14286
|
static maxLevel = 5;
|
|
@@ -16229,24 +15618,30 @@ function signedArea(data, start, end, dim) {
|
|
|
16229
15618
|
return sum;
|
|
16230
15619
|
}
|
|
16231
15620
|
|
|
16232
|
-
const buildLine = (startX, startY, endX, endY, width
|
|
15621
|
+
const buildLine = (startX, startY, endX, endY, width) => {
|
|
16233
15622
|
const points = [startX, startY, endX, endY];
|
|
16234
15623
|
const distance = width / 2;
|
|
16235
|
-
const index = vertices.length / 6;
|
|
16236
15624
|
const perpA = new Vector(startX - endX, startY - endY).perp().normalize().multiply(distance);
|
|
16237
15625
|
const perpB = new Vector(endX - startX, endY - startY).perp().normalize().multiply(distance);
|
|
16238
|
-
vertices
|
|
16239
|
-
|
|
16240
|
-
|
|
16241
|
-
|
|
16242
|
-
|
|
16243
|
-
|
|
15626
|
+
const vertices = new Float32Array([
|
|
15627
|
+
startX - perpA.x, startY - perpA.y, // 0: start-left
|
|
15628
|
+
startX + perpA.x, startY + perpA.y, // 1: start-right
|
|
15629
|
+
endX - perpB.x, endY - perpB.y, // 2: end-left
|
|
15630
|
+
endX + perpB.x, endY + perpB.y, // 3: end-right
|
|
15631
|
+
]);
|
|
15632
|
+
perpA.destroy();
|
|
15633
|
+
perpB.destroy();
|
|
15634
|
+
const indices = new Uint16Array([0, 1, 3, 0, 3, 2]);
|
|
15635
|
+
return { vertices, indices, points };
|
|
16244
15636
|
};
|
|
16245
|
-
const buildPath = (points, width
|
|
15637
|
+
const buildPath = (points, width) => {
|
|
16246
15638
|
if (points.length < 4) {
|
|
16247
15639
|
throw new Error('At least two X/Y pairs are required to build a line.');
|
|
16248
15640
|
}
|
|
16249
|
-
const lineWidth = width / 2
|
|
15641
|
+
const lineWidth = width / 2;
|
|
15642
|
+
const firstPoint = new Vector(points[0], points[1]);
|
|
15643
|
+
const lastPoint = new Vector(points[points.length - 2], points[points.length - 1]);
|
|
15644
|
+
const outlinePoints = points;
|
|
16250
15645
|
if (firstPoint.x === lastPoint.x && firstPoint.y === lastPoint.y) {
|
|
16251
15646
|
points = points.slice();
|
|
16252
15647
|
points.pop();
|
|
@@ -16257,9 +15652,10 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16257
15652
|
points.unshift(midPointX, midPointY);
|
|
16258
15653
|
points.push(midPointX, midPointY);
|
|
16259
15654
|
}
|
|
15655
|
+
firstPoint.destroy();
|
|
15656
|
+
lastPoint.destroy();
|
|
16260
15657
|
const length = points.length / 2;
|
|
16261
|
-
|
|
16262
|
-
let indexStart = vertices.length / 6;
|
|
15658
|
+
const stripVertices = [];
|
|
16263
15659
|
let p1x = points[0];
|
|
16264
15660
|
let p1y = points[1];
|
|
16265
15661
|
let p2x = points[2];
|
|
@@ -16277,8 +15673,8 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16277
15673
|
perpy /= dist;
|
|
16278
15674
|
perpx *= lineWidth;
|
|
16279
15675
|
perpy *= lineWidth;
|
|
16280
|
-
|
|
16281
|
-
|
|
15676
|
+
stripVertices.push(p1x - perpx, p1y - perpy);
|
|
15677
|
+
stripVertices.push(p1x + perpx, p1y + perpy);
|
|
16282
15678
|
for (let i = 1; i < length - 1; i++) {
|
|
16283
15679
|
p1x = points[(i - 1) * 2];
|
|
16284
15680
|
p1y = points[((i - 1) * 2) + 1];
|
|
@@ -16309,8 +15705,8 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16309
15705
|
let denom = (a1 * b2) - (a2 * b1);
|
|
16310
15706
|
if (Math.abs(denom) < 0.1) {
|
|
16311
15707
|
denom += 10.1;
|
|
16312
|
-
|
|
16313
|
-
|
|
15708
|
+
stripVertices.push(p2x - perpx, p2y - perpy);
|
|
15709
|
+
stripVertices.push(p2x + perpx, p2y + perpy);
|
|
16314
15710
|
continue;
|
|
16315
15711
|
}
|
|
16316
15712
|
const px = ((b1 * c2) - (b2 * c1)) / denom;
|
|
@@ -16324,14 +15720,13 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16324
15720
|
perp3y /= dist;
|
|
16325
15721
|
perp3x *= lineWidth;
|
|
16326
15722
|
perp3y *= lineWidth;
|
|
16327
|
-
|
|
16328
|
-
|
|
16329
|
-
|
|
16330
|
-
indexCount++;
|
|
15723
|
+
stripVertices.push(p2x - perp3x, p2y - perp3y);
|
|
15724
|
+
stripVertices.push(p2x + perp3x, p2y + perp3y);
|
|
15725
|
+
stripVertices.push(p2x - perp3x, p2y - perp3y);
|
|
16331
15726
|
}
|
|
16332
15727
|
else {
|
|
16333
|
-
|
|
16334
|
-
|
|
15728
|
+
stripVertices.push(px, py);
|
|
15729
|
+
stripVertices.push(p2x - (px - p2x), p2y - (py - p2y));
|
|
16335
15730
|
}
|
|
16336
15731
|
}
|
|
16337
15732
|
p1x = points[(length - 2) * 2];
|
|
@@ -16345,70 +15740,112 @@ const buildPath = (points, width, vertices = [], indices = []) => {
|
|
|
16345
15740
|
perpy /= dist;
|
|
16346
15741
|
perpx *= lineWidth;
|
|
16347
15742
|
perpy *= lineWidth;
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
indices.
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
|
|
16354
|
-
|
|
16355
|
-
|
|
15743
|
+
stripVertices.push(p2x - perpx, p2y - perpy);
|
|
15744
|
+
stripVertices.push(p2x + perpx, p2y + perpy);
|
|
15745
|
+
// Convert strip-style vertex sequence to triangle-list indices.
|
|
15746
|
+
// For N strip vertices (N = stripVertices.length / 2), each i in [0, N-3]
|
|
15747
|
+
// produces a triangle. Even i: (i, i+1, i+2). Odd i: (i+1, i, i+2).
|
|
15748
|
+
// This preserves the same winding the original triangle-strip pipeline saw.
|
|
15749
|
+
const stripVertexCount = stripVertices.length / 2;
|
|
15750
|
+
const vertices = new Float32Array(stripVertices);
|
|
15751
|
+
const triangleCount = stripVertexCount >= 3 ? stripVertexCount - 2 : 0;
|
|
15752
|
+
const indices = new Uint16Array(triangleCount * 3);
|
|
15753
|
+
for (let i = 0; i < triangleCount; i++) {
|
|
15754
|
+
const base = i * 3;
|
|
15755
|
+
if ((i & 1) === 0) {
|
|
15756
|
+
indices[base] = i;
|
|
15757
|
+
indices[base + 1] = i + 1;
|
|
15758
|
+
indices[base + 2] = i + 2;
|
|
15759
|
+
}
|
|
15760
|
+
else {
|
|
15761
|
+
indices[base] = i + 1;
|
|
15762
|
+
indices[base + 1] = i;
|
|
15763
|
+
indices[base + 2] = i + 2;
|
|
15764
|
+
}
|
|
15765
|
+
}
|
|
15766
|
+
return { vertices, indices, points: outlinePoints };
|
|
16356
15767
|
};
|
|
16357
|
-
const buildCircle = (centerX, centerY, radius
|
|
16358
|
-
const length = Math.floor(15 * Math.sqrt(radius + radius))
|
|
16359
|
-
|
|
16360
|
-
|
|
16361
|
-
|
|
16362
|
-
|
|
15768
|
+
const buildCircle = (centerX, centerY, radius) => {
|
|
15769
|
+
const length = Math.floor(15 * Math.sqrt(radius + radius));
|
|
15770
|
+
const segment = (Math.PI * 2) / length;
|
|
15771
|
+
const points = [];
|
|
15772
|
+
// 1 center vertex + N perimeter vertices.
|
|
15773
|
+
const vertices = new Float32Array((length + 1) * 2);
|
|
15774
|
+
vertices[0] = centerX;
|
|
15775
|
+
vertices[1] = centerY;
|
|
15776
|
+
for (let i = 0; i < length; i++) {
|
|
15777
|
+
const segmentX = centerX + (Math.sin(segment * i) * radius);
|
|
15778
|
+
const segmentY = centerY + (Math.cos(segment * i) * radius);
|
|
16363
15779
|
points.push(segmentX, segmentY);
|
|
16364
|
-
|
|
16365
|
-
vertices
|
|
16366
|
-
|
|
15780
|
+
const offset = (i + 1) * 2;
|
|
15781
|
+
vertices[offset] = segmentX;
|
|
15782
|
+
vertices[offset + 1] = segmentY;
|
|
15783
|
+
}
|
|
15784
|
+
const indices = new Uint16Array(length * 3);
|
|
15785
|
+
for (let i = 0; i < length; i++) {
|
|
15786
|
+
const base = i * 3;
|
|
15787
|
+
indices[base] = 0;
|
|
15788
|
+
indices[base + 1] = i + 1;
|
|
15789
|
+
indices[base + 2] = i + 2 > length ? 1 : i + 2;
|
|
16367
15790
|
}
|
|
16368
|
-
indices
|
|
16369
|
-
return new Geometry({ vertices, indices, points });
|
|
15791
|
+
return { vertices, indices, points };
|
|
16370
15792
|
};
|
|
16371
|
-
const buildEllipse = (centerX, centerY, radiusX, radiusY
|
|
16372
|
-
const length = Math.floor(15 * Math.sqrt(radiusX + radiusY))
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
15793
|
+
const buildEllipse = (centerX, centerY, radiusX, radiusY) => {
|
|
15794
|
+
const length = Math.floor(15 * Math.sqrt(radiusX + radiusY));
|
|
15795
|
+
const segment = (Math.PI * 2) / length;
|
|
15796
|
+
const points = [];
|
|
15797
|
+
const vertices = new Float32Array((length + 1) * 2);
|
|
15798
|
+
vertices[0] = centerX;
|
|
15799
|
+
vertices[1] = centerY;
|
|
15800
|
+
for (let i = 0; i < length; i++) {
|
|
15801
|
+
const segmentX = centerX + (Math.sin(segment * i) * radiusX);
|
|
15802
|
+
const segmentY = centerY + (Math.cos(segment * i) * radiusY);
|
|
16377
15803
|
points.push(segmentX, segmentY);
|
|
16378
|
-
|
|
16379
|
-
vertices
|
|
16380
|
-
|
|
15804
|
+
const offset = (i + 1) * 2;
|
|
15805
|
+
vertices[offset] = segmentX;
|
|
15806
|
+
vertices[offset + 1] = segmentY;
|
|
15807
|
+
}
|
|
15808
|
+
const indices = new Uint16Array(length * 3);
|
|
15809
|
+
for (let i = 0; i < length; i++) {
|
|
15810
|
+
const base = i * 3;
|
|
15811
|
+
indices[base] = 0;
|
|
15812
|
+
indices[base + 1] = i + 1;
|
|
15813
|
+
indices[base + 2] = i + 2 > length ? 1 : i + 2;
|
|
16381
15814
|
}
|
|
16382
|
-
indices
|
|
16383
|
-
return new Geometry({ vertices, indices, points });
|
|
15815
|
+
return { vertices, indices, points };
|
|
16384
15816
|
};
|
|
16385
|
-
const buildPolygon = (points
|
|
15817
|
+
const buildPolygon = (points) => {
|
|
16386
15818
|
if (points.length < 6) {
|
|
16387
15819
|
throw new Error('At least three X/Y pairs are required to build a polygon.');
|
|
16388
15820
|
}
|
|
16389
|
-
const
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
indices.push(triangles[i + 2] + index);
|
|
16396
|
-
indices.push(triangles[i + 2] + index);
|
|
16397
|
-
}
|
|
16398
|
-
for (let i = 0; i < length; i++) {
|
|
16399
|
-
vertices.push(points[i * 2], points[(i * 2) + 1]);
|
|
16400
|
-
}
|
|
15821
|
+
const length = points.length / 2;
|
|
15822
|
+
const triangles = earcut(points, [], 2);
|
|
15823
|
+
const vertices = new Float32Array(points.length);
|
|
15824
|
+
for (let i = 0; i < length; i++) {
|
|
15825
|
+
vertices[i * 2] = points[i * 2];
|
|
15826
|
+
vertices[(i * 2) + 1] = points[(i * 2) + 1];
|
|
16401
15827
|
}
|
|
16402
|
-
|
|
15828
|
+
const indices = triangles ? new Uint16Array(triangles) : new Uint16Array(0);
|
|
15829
|
+
return { vertices, indices, points };
|
|
16403
15830
|
};
|
|
16404
|
-
const buildRectangle = (x, y, width, height
|
|
16405
|
-
|
|
16406
|
-
vertices
|
|
16407
|
-
|
|
16408
|
-
|
|
15831
|
+
const buildRectangle = (x, y, width, height) => {
|
|
15832
|
+
// 4 vertices: TL, TR, BL, BR. Triangles [0, 1, 2, 1, 3, 2] (clockwise).
|
|
15833
|
+
const vertices = new Float32Array([
|
|
15834
|
+
x, y, // 0 TL
|
|
15835
|
+
x + width, y, // 1 TR
|
|
15836
|
+
x, y + height, // 2 BL
|
|
15837
|
+
x + width, y + height, // 3 BR
|
|
15838
|
+
]);
|
|
15839
|
+
const indices = new Uint16Array([0, 1, 2, 1, 3, 2]);
|
|
15840
|
+
// Outline points walk the perimeter (TL -> TR -> BR -> BL).
|
|
15841
|
+
const points = [x, y, x + width, y, x + width, y + height, x, y + height];
|
|
15842
|
+
return { vertices, indices, points };
|
|
16409
15843
|
};
|
|
16410
15844
|
const buildStar = (centerX, centerY, points, radius, innerRadius = radius / 2, rotation = 0) => {
|
|
16411
|
-
const startAngle = (Math.PI / -2) + rotation
|
|
15845
|
+
const startAngle = (Math.PI / -2) + rotation;
|
|
15846
|
+
const length = points * 2;
|
|
15847
|
+
const delta = tau / length;
|
|
15848
|
+
const path = [];
|
|
16412
15849
|
for (let i = 0; i < length; i++) {
|
|
16413
15850
|
const angle = startAngle + (i * delta);
|
|
16414
15851
|
const rad = i % 2 ? innerRadius : radius;
|
|
@@ -17251,23 +16688,6 @@ class UniversalEmitter {
|
|
|
17251
16688
|
}
|
|
17252
16689
|
}
|
|
17253
16690
|
|
|
17254
|
-
class CircleGeometry extends Geometry {
|
|
17255
|
-
constructor(centerX, centerY, radius) {
|
|
17256
|
-
const length = Math.floor(15 * Math.sqrt(radius + radius)), segment = (Math.PI * 2) / length, vertices = [], indices = [], points = [];
|
|
17257
|
-
let index = vertices.length / 6;
|
|
17258
|
-
indices.push(index);
|
|
17259
|
-
for (let i = 0; i < length + 1; i++) {
|
|
17260
|
-
const segmentX = centerX + (Math.sin(segment * i) * radius), segmentY = centerY + (Math.cos(segment * i) * radius);
|
|
17261
|
-
points.push(segmentX, segmentY);
|
|
17262
|
-
vertices.push(centerX, centerY);
|
|
17263
|
-
vertices.push(segmentX, segmentY);
|
|
17264
|
-
indices.push(index++, index++);
|
|
17265
|
-
}
|
|
17266
|
-
indices.push(index - 1);
|
|
17267
|
-
super({ vertices, indices, points });
|
|
17268
|
-
}
|
|
17269
|
-
}
|
|
17270
|
-
|
|
17271
16691
|
class Graphics extends Container {
|
|
17272
16692
|
_lineWidth = 0;
|
|
17273
16693
|
_lineColor = new Color();
|
|
@@ -17298,14 +16718,14 @@ class Graphics extends Container {
|
|
|
17298
16718
|
return super.getChildAt(index);
|
|
17299
16719
|
}
|
|
17300
16720
|
addChild(child) {
|
|
17301
|
-
if (!(child instanceof
|
|
17302
|
-
throw new Error('Graphics can only contain
|
|
16721
|
+
if (!(child instanceof Mesh)) {
|
|
16722
|
+
throw new Error('Graphics can only contain Mesh children.');
|
|
17303
16723
|
}
|
|
17304
16724
|
return super.addChild(child);
|
|
17305
16725
|
}
|
|
17306
16726
|
addChildAt(child, index) {
|
|
17307
|
-
if (!(child instanceof
|
|
17308
|
-
throw new Error('Graphics can only contain
|
|
16727
|
+
if (!(child instanceof Mesh)) {
|
|
16728
|
+
throw new Error('Graphics can only contain Mesh children.');
|
|
17309
16729
|
}
|
|
17310
16730
|
return super.addChildAt(child, index);
|
|
17311
16731
|
}
|
|
@@ -17401,50 +16821,52 @@ class Graphics extends Container {
|
|
|
17401
16821
|
return this;
|
|
17402
16822
|
}
|
|
17403
16823
|
drawLine(startX, startY, endX, endY) {
|
|
17404
|
-
|
|
16824
|
+
const data = buildLine(startX, startY, endX, endY, this._lineWidth);
|
|
16825
|
+
this.addChild(this._createMesh(data, this._lineColor));
|
|
17405
16826
|
return this;
|
|
17406
16827
|
}
|
|
17407
16828
|
drawPath(path) {
|
|
17408
|
-
|
|
16829
|
+
const data = buildPath(path, this._lineWidth);
|
|
16830
|
+
this.addChild(this._createMesh(data, this._lineColor));
|
|
17409
16831
|
return this;
|
|
17410
16832
|
}
|
|
17411
16833
|
drawPolygon(path) {
|
|
17412
|
-
const
|
|
17413
|
-
this.addChild(
|
|
16834
|
+
const data = buildPolygon(path);
|
|
16835
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17414
16836
|
if (this._lineWidth > 0) {
|
|
17415
|
-
this.drawPath(
|
|
16837
|
+
this.drawPath(data.points);
|
|
17416
16838
|
}
|
|
17417
16839
|
return this;
|
|
17418
16840
|
}
|
|
17419
16841
|
drawCircle(centerX, centerY, radius) {
|
|
17420
|
-
const
|
|
17421
|
-
this.addChild(
|
|
16842
|
+
const data = buildCircle(centerX, centerY, radius);
|
|
16843
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17422
16844
|
if (this._lineWidth > 0) {
|
|
17423
|
-
this.drawPath(
|
|
16845
|
+
this.drawPath(data.points);
|
|
17424
16846
|
}
|
|
17425
16847
|
return this;
|
|
17426
16848
|
}
|
|
17427
16849
|
drawEllipse(centerX, centerY, radiusX, radiusY) {
|
|
17428
|
-
const
|
|
17429
|
-
this.addChild(
|
|
16850
|
+
const data = buildEllipse(centerX, centerY, radiusX, radiusY);
|
|
16851
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17430
16852
|
if (this._lineWidth > 0) {
|
|
17431
|
-
this.drawPath(
|
|
16853
|
+
this.drawPath(data.points);
|
|
17432
16854
|
}
|
|
17433
16855
|
return this;
|
|
17434
16856
|
}
|
|
17435
16857
|
drawRectangle(x, y, width, height) {
|
|
17436
|
-
const
|
|
17437
|
-
this.addChild(
|
|
16858
|
+
const data = buildRectangle(x, y, width, height);
|
|
16859
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17438
16860
|
if (this._lineWidth > 0) {
|
|
17439
|
-
this.drawPath(
|
|
16861
|
+
this.drawPath(data.points);
|
|
17440
16862
|
}
|
|
17441
16863
|
return this;
|
|
17442
16864
|
}
|
|
17443
16865
|
drawStar(centerX, centerY, points, radius, innerRadius = radius / 2, rotation = 0) {
|
|
17444
|
-
const
|
|
17445
|
-
this.addChild(
|
|
16866
|
+
const data = buildStar(centerX, centerY, points, radius, innerRadius, rotation);
|
|
16867
|
+
this.addChild(this._createMesh(data, this._fillColor));
|
|
17446
16868
|
if (this._lineWidth > 0) {
|
|
17447
|
-
this.drawPath(
|
|
16869
|
+
this.drawPath(data.points);
|
|
17448
16870
|
}
|
|
17449
16871
|
return this;
|
|
17450
16872
|
}
|
|
@@ -17463,6 +16885,14 @@ class Graphics extends Container {
|
|
|
17463
16885
|
this._fillColor.destroy();
|
|
17464
16886
|
this._currentPoint.destroy();
|
|
17465
16887
|
}
|
|
16888
|
+
_createMesh(data, color) {
|
|
16889
|
+
const mesh = new Mesh({
|
|
16890
|
+
vertices: data.vertices,
|
|
16891
|
+
indices: data.indices,
|
|
16892
|
+
});
|
|
16893
|
+
mesh.tint = color;
|
|
16894
|
+
return mesh;
|
|
16895
|
+
}
|
|
17466
16896
|
}
|
|
17467
16897
|
|
|
17468
16898
|
class Spritesheet {
|
|
@@ -19079,5 +18509,5 @@ const createRapierPhysicsWorld = async (options = {}) => {
|
|
|
19079
18509
|
return await RapierPhysicsWorld.create(options);
|
|
19080
18510
|
};
|
|
19081
18511
|
|
|
19082
|
-
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, ChannelOffset, ChannelSize, Circle,
|
|
18512
|
+
export { AbstractAssetFactory, AbstractMedia, AbstractWebGl2BatchedRenderer, AbstractWebGl2Renderer, AbstractWebGpuRenderer, AnimatedSprite, Application, ApplicationStatus, ArcadeStickGamepadMapping, AudioAnalyser, BinaryFactory, BlendModes, BlurFilter, Bounds, BufferTypes, BufferUsage, BundleLoadError, CacheFirstStrategy, CallbackRenderPass, Capabilities, ChannelOffset, ChannelSize, Circle, Clock, CollisionType, Color, ColorAffector, ColorFilter, Container, Drawable, Ellipse, FactoryRegistry, Filter, Flags, FontFactory, ForceAffector, GameCubeGamepadMapping, Gamepad, GamepadChannel, GamepadControl, GamepadMapping, GamepadMappingFamily, GamepadPromptLayouts, GenericDualAnalogGamepadMapping, Graphics, ImageFactory, IndexedDbDatabase, IndexedDbStore, Input, InputManager, Interval, JoyConLeftGamepadMapping, JoyConRightGamepadMapping, Json, JsonFactory, Keyboard, Line, Loader, Matrix, Mesh, Music, MusicFactory, NetworkOnlyStrategy, ObservableSize, ObservableVector, Particle, ParticleOptions, ParticleSystem, PlayStationGamepadMapping, Pointer, PointerState, PointerStateFlag, PolarVector, Polygon, Quadtree, Random, RapierPhysicsBinding, RapierPhysicsWorld, Rectangle, RenderBackendType, RenderNode, RenderTarget, RenderTargetPass, RenderTexture, RendererRegistry, RenderingPrimitives, Sampler, ScaleAffector, ScaleModes, Scene, SceneManager, SceneNode, Segment, Shader, ShaderAttribute, ShaderPrimitives, ShaderUniform, Signal, Size, Sound, SoundFactory, Sprite, SpriteFlags, Spritesheet, SteamControllerGamepadMapping, SvgAsset, SvgFactory, SwitchProGamepadMapping, Text, TextAsset, TextFactory, TextStyle, Texture, TextureFactory, Time, Timer, TorqueAffector, UniversalEmitter, Vector, Video, VideoFactory, View, ViewFlags, VoronoiRegion, VttAsset, VttFactory, WasmFactory, WebGl2Backend, WebGl2MeshRenderer, WebGl2ParticleRenderer, WebGl2RenderBuffer, WebGl2ShaderBlock, WebGl2SpriteRenderer, WebGl2VertexArrayObject, WebGpuBackend, WebGpuMeshRenderer, WebGpuParticleRenderer, WebGpuSpriteRenderer, WrapModes, XboxGamepadMapping, bezierCurveTo, buildCircle, buildEllipse, buildLine, buildPath, buildPolygon, buildRectangle, buildStar, builtInGamepadDefinitions, canvasSourceToDataUrl, clamp, createRapierPhysicsWorld, createRenderStats, createWebGl2ShaderProgram, decodeAudioData, defineAssetManifest, degreesPerRadian, degreesToRadians, determineMimeType, emptyArrayBuffer, getAudioContext, getCanvasSourceSize, getCollisionCircleCircle, getCollisionCircleRectangle, getCollisionPolygonCircle, getCollisionRectangleRectangle, getCollisionSat, getDistance, getOfflineAudioContext, getPreciseTime, getTextureSourceSize, getVoronoiRegion$1 as getVoronoiRegion, getWebGpuBlendState, hours, inRange, intersectionCircleCircle, intersectionCircleEllipse, intersectionCirclePoly, intersectionEllipseEllipse, intersectionEllipsePoly, intersectionLineCircle, intersectionLineEllipse, intersectionLineLine, intersectionLinePoly, intersectionLineRect, intersectionPointCircle, intersectionPointEllipse, intersectionPointLine, intersectionPointPoint, intersectionPointPoly, intersectionPointRect, intersectionPolyPoly, intersectionRectCircle, intersectionRectEllipse, intersectionRectPoly, intersectionRectRect, intersectionSat, isAudioContextReady, isPowerOfTwo, lerp, matchesIds, milliseconds, minutes, noop$1 as noop, normalizeIds, onAudioContextReady, parseGamepadDescriptor, quadraticCurveTo, radiansPerDegree, radiansToDegrees, rand, removeArrayItems, resetRenderStats, resolveDefinition, resolveGamepadDefinition, seconds, sign$1 as sign, stopEvent, supportsCodec, supportsEventOptions, supportsIndexedDb, supportsPointerEvents, supportsTouchEvents, supportsWebAudio, tau, trimRotation, webGl2PrimitiveArrayConstructors, webGl2PrimitiveByteSizeMapping, webGl2PrimitiveTypeNames };
|
|
19083
18513
|
//# sourceMappingURL=exo.esm.js.map
|