melonjs 19.2.0 → 19.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -10
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/index.js +1623 -529
- package/build/index.js.map +4 -4
- package/build/physics/bounds.d.ts +5 -2
- package/build/physics/bounds.d.ts.map +1 -1
- package/build/renderable/container.d.ts +1 -1
- package/build/renderable/container.d.ts.map +1 -1
- package/build/renderable/light2d.d.ts +128 -18
- package/build/renderable/light2d.d.ts.map +1 -1
- package/build/renderable/sprite.d.ts +38 -6
- package/build/renderable/sprite.d.ts.map +1 -1
- package/build/state/stage.d.ts +65 -9
- package/build/state/stage.d.ts.map +1 -1
- package/build/video/buffer/vertex.d.ts +2 -1
- package/build/video/buffer/vertex.d.ts.map +1 -1
- package/build/video/canvas/canvas_renderer.d.ts +2 -0
- package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
- package/build/video/renderer.d.ts +62 -0
- package/build/video/renderer.d.ts.map +1 -1
- package/build/video/renderstate.d.ts +20 -0
- package/build/video/renderstate.d.ts.map +1 -1
- package/build/video/texture/atlas.d.ts +26 -2
- package/build/video/texture/atlas.d.ts.map +1 -1
- package/build/video/webgl/batchers/batcher.d.ts +6 -0
- package/build/video/webgl/batchers/batcher.d.ts.map +1 -1
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts +109 -0
- package/build/video/webgl/batchers/lit_quad_batcher.d.ts.map +1 -0
- package/build/video/webgl/batchers/quad_batcher.d.ts +19 -1
- package/build/video/webgl/batchers/quad_batcher.d.ts.map +1 -1
- package/build/video/webgl/effects/radialGradient.d.ts +105 -0
- package/build/video/webgl/effects/radialGradient.d.ts.map +1 -0
- package/build/video/webgl/glshader.d.ts.map +1 -1
- package/build/video/webgl/lighting/constants.d.ts +13 -0
- package/build/video/webgl/lighting/constants.d.ts.map +1 -0
- package/build/video/webgl/lighting/pack.d.ts +76 -0
- package/build/video/webgl/lighting/pack.d.ts.map +1 -0
- package/build/video/webgl/shaders/multitexture-lit.d.ts +23 -0
- package/build/video/webgl/shaders/multitexture-lit.d.ts.map +1 -0
- package/build/video/webgl/webgl_renderer.d.ts +17 -0
- package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* melonJS Game Engine - 19.
|
|
2
|
+
* melonJS Game Engine - 19.3.0
|
|
3
3
|
* http://www.melonjs.org
|
|
4
4
|
* melonjs is licensed under the MIT License.
|
|
5
5
|
* http://www.opensource.org/licenses/mit-license
|
|
@@ -750,8 +750,8 @@ var require_weak_map_basic_detection = __commonJS({
|
|
|
750
750
|
"use strict";
|
|
751
751
|
var globalThis2 = require_global_this();
|
|
752
752
|
var isCallable = require_is_callable();
|
|
753
|
-
var
|
|
754
|
-
module.exports = isCallable(
|
|
753
|
+
var WeakMap2 = globalThis2.WeakMap;
|
|
754
|
+
module.exports = isCallable(WeakMap2) && /native code/.test(String(WeakMap2));
|
|
755
755
|
}
|
|
756
756
|
});
|
|
757
757
|
|
|
@@ -790,7 +790,7 @@ var require_internal_state = __commonJS({
|
|
|
790
790
|
var hiddenKeys = require_hidden_keys();
|
|
791
791
|
var OBJECT_ALREADY_INITIALIZED = "Object already initialized";
|
|
792
792
|
var TypeError2 = globalThis2.TypeError;
|
|
793
|
-
var
|
|
793
|
+
var WeakMap2 = globalThis2.WeakMap;
|
|
794
794
|
var set;
|
|
795
795
|
var get2;
|
|
796
796
|
var has2;
|
|
@@ -807,7 +807,7 @@ var require_internal_state = __commonJS({
|
|
|
807
807
|
};
|
|
808
808
|
};
|
|
809
809
|
if (NATIVE_WEAK_MAP || shared.state) {
|
|
810
|
-
store = shared.state || (shared.state = new
|
|
810
|
+
store = shared.state || (shared.state = new WeakMap2());
|
|
811
811
|
store.get = store.get;
|
|
812
812
|
store.has = store.has;
|
|
813
813
|
store.set = store.set;
|
|
@@ -4639,6 +4639,7 @@ var pointPool = createPool((x, y) => {
|
|
|
4639
4639
|
});
|
|
4640
4640
|
|
|
4641
4641
|
// src/physics/bounds.ts
|
|
4642
|
+
var _addFrameScratch = new Point();
|
|
4642
4643
|
var Bounds = class _Bounds {
|
|
4643
4644
|
_center;
|
|
4644
4645
|
type;
|
|
@@ -4865,20 +4866,41 @@ var Bounds = class _Bounds {
|
|
|
4865
4866
|
this.max.y = Math.max(this.max.y, p.y);
|
|
4866
4867
|
}
|
|
4867
4868
|
/**
|
|
4868
|
-
*
|
|
4869
|
+
* Expands this bounds to include the axis-aligned bounding box of
|
|
4870
|
+
* the given rect's four corners, optionally transformed through `m`
|
|
4871
|
+
* first. With a non-identity `m` (rotation, scale, etc.) the result
|
|
4872
|
+
* is the AABB of the transformed quad, not a transformed AABB.
|
|
4869
4873
|
* @param x0 - The left x coordinate of the quad.
|
|
4870
4874
|
* @param y0 - The top y coordinate of the quad.
|
|
4871
4875
|
* @param x1 - The right x coordinate of the quad.
|
|
4872
4876
|
* @param y1 - The bottom y coordinate of the quad.
|
|
4873
|
-
* @param [m] - An optional transform to
|
|
4877
|
+
* @param [m] - An optional transform applied to each corner before inclusion.
|
|
4874
4878
|
*/
|
|
4875
4879
|
addFrame(x0, y0, x1, y1, m) {
|
|
4876
|
-
|
|
4880
|
+
if (m === void 0 || m.isIdentity()) {
|
|
4881
|
+
const minX = Math.min(x0, x1);
|
|
4882
|
+
const maxX = Math.max(x0, x1);
|
|
4883
|
+
const minY = Math.min(y0, y1);
|
|
4884
|
+
const maxY = Math.max(y0, y1);
|
|
4885
|
+
if (minX < this.min.x) {
|
|
4886
|
+
this.min.x = minX;
|
|
4887
|
+
}
|
|
4888
|
+
if (maxX > this.max.x) {
|
|
4889
|
+
this.max.x = maxX;
|
|
4890
|
+
}
|
|
4891
|
+
if (minY < this.min.y) {
|
|
4892
|
+
this.min.y = minY;
|
|
4893
|
+
}
|
|
4894
|
+
if (maxY > this.max.y) {
|
|
4895
|
+
this.max.y = maxY;
|
|
4896
|
+
}
|
|
4897
|
+
return;
|
|
4898
|
+
}
|
|
4899
|
+
const v = _addFrameScratch;
|
|
4877
4900
|
this.addPoint(v.set(x0, y0), m);
|
|
4878
4901
|
this.addPoint(v.set(x1, y0), m);
|
|
4879
4902
|
this.addPoint(v.set(x0, y1), m);
|
|
4880
4903
|
this.addPoint(v.set(x1, y1), m);
|
|
4881
|
-
pointPool.release(v);
|
|
4882
4904
|
}
|
|
4883
4905
|
contains(xOrVectorOrBounds, y) {
|
|
4884
4906
|
let _x1;
|
|
@@ -14138,10 +14160,10 @@ var Container = class _Container extends Renderable {
|
|
|
14138
14160
|
draw(renderer2, viewport) {
|
|
14139
14161
|
const bounds = this.getBounds();
|
|
14140
14162
|
this.drawCount = 0;
|
|
14141
|
-
if (this.root === false && this.clipping === true && bounds.isFinite() === true) {
|
|
14142
|
-
renderer2.clipRect(bounds.left, bounds.top, bounds.width, bounds.height);
|
|
14143
|
-
}
|
|
14144
14163
|
renderer2.translate(this.pos.x, this.pos.y);
|
|
14164
|
+
if (this.root === false && this.clipping === true && bounds.isFinite() === true && Number.isFinite(this.width) === true && Number.isFinite(this.height) === true) {
|
|
14165
|
+
renderer2.clipRect(0, 0, this.width, this.height);
|
|
14166
|
+
}
|
|
14145
14167
|
if (this.backgroundColor.alpha > 1 / 255) {
|
|
14146
14168
|
renderer2.clearColor(this.backgroundColor);
|
|
14147
14169
|
}
|
|
@@ -15366,6 +15388,32 @@ var RenderState = class {
|
|
|
15366
15388
|
}
|
|
15367
15389
|
this._stackDepth = depth + 1;
|
|
15368
15390
|
}
|
|
15391
|
+
/**
|
|
15392
|
+
* Inspect the scissor box that the next `restore()` would install,
|
|
15393
|
+
* without mutating any state. Lets renderers detect whether a
|
|
15394
|
+
* pending `restore()` will actually change the scissor (and
|
|
15395
|
+
* decide, e.g., whether to flush GPU work first).
|
|
15396
|
+
*
|
|
15397
|
+
* Returns:
|
|
15398
|
+
* - `Int32Array` (length 4) — `[x, y, width, height]` of the
|
|
15399
|
+
* saved scissor when scissor was active at the matching `save()`
|
|
15400
|
+
* call.
|
|
15401
|
+
* - `null` — when the saved state had scissor disabled, or the
|
|
15402
|
+
* stack is empty. Treat this as "next scissor will be inactive".
|
|
15403
|
+
*
|
|
15404
|
+
* The returned array is a **live reference into the internal
|
|
15405
|
+
* stack** — zero allocation on a hot path. Callers MUST treat it
|
|
15406
|
+
* as read-only; mutating it corrupts subsequent `restore()` calls.
|
|
15407
|
+
* @ignore
|
|
15408
|
+
* @returns {Int32Array | null}
|
|
15409
|
+
*/
|
|
15410
|
+
peekScissor() {
|
|
15411
|
+
const depth = this._stackDepth - 1;
|
|
15412
|
+
if (depth < 0 || !this._scissorActive[depth]) {
|
|
15413
|
+
return null;
|
|
15414
|
+
}
|
|
15415
|
+
return this._scissorStack[depth];
|
|
15416
|
+
}
|
|
15369
15417
|
/**
|
|
15370
15418
|
* Restore state from the stack.
|
|
15371
15419
|
* Color, tint, transform, and scissor are restored in place.
|
|
@@ -15466,6 +15514,8 @@ var Renderer = class {
|
|
|
15466
15514
|
this.maskLevel = 0;
|
|
15467
15515
|
this.projectionMatrix = new Matrix3d();
|
|
15468
15516
|
this.uvOffset = 0;
|
|
15517
|
+
this.currentNormalMap = null;
|
|
15518
|
+
this.activeLightCount = 0;
|
|
15469
15519
|
}
|
|
15470
15520
|
/**
|
|
15471
15521
|
* @type {string}
|
|
@@ -15686,6 +15736,62 @@ var Renderer = class {
|
|
|
15686
15736
|
setBlendMode(mode = "normal") {
|
|
15687
15737
|
this.currentBlendMode = mode;
|
|
15688
15738
|
}
|
|
15739
|
+
/**
|
|
15740
|
+
* Upload the active scene lights to the lit sprite pipeline.
|
|
15741
|
+
*
|
|
15742
|
+
* Called once per camera per frame by `Camera2d.draw()` (after the
|
|
15743
|
+
* FBO is bound, before the world tree walk fires `Sprite.draw` for
|
|
15744
|
+
* any normal-mapped sprite). The WebGL renderer overrides this to
|
|
15745
|
+
* pack the lights into the lit shader's uniform buffers; the Canvas
|
|
15746
|
+
* renderer cannot do per-pixel normal-map lighting and silently
|
|
15747
|
+
* ignores the call. The first time a non-empty light list is passed
|
|
15748
|
+
* in Canvas mode, a one-shot console warning is emitted.
|
|
15749
|
+
*
|
|
15750
|
+
* Stage stays renderer-agnostic by passing the raw scene data —
|
|
15751
|
+
* lights iterable and ambient color — and letting the renderer
|
|
15752
|
+
* decide how to encode them.
|
|
15753
|
+
* @param {Iterable<object>} [lights] - active `Light2d` instances; falsy/empty no-ops
|
|
15754
|
+
* @param {object} [ambient] - ambient lighting color (0..255 RGB)
|
|
15755
|
+
* @param {number} [translateX=0] - world-to-screen X translate (matches `Camera2d.draw()`)
|
|
15756
|
+
* @param {number} [translateY=0] - world-to-screen Y translate
|
|
15757
|
+
*/
|
|
15758
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
15759
|
+
setLightUniforms(lights, ambient, translateX, translateY) {
|
|
15760
|
+
if (this._litPipelineWarned || !lights) {
|
|
15761
|
+
return;
|
|
15762
|
+
}
|
|
15763
|
+
const hasAny = typeof lights.size === "number" && lights.size > 0 || typeof lights.length === "number" && lights.length > 0 || typeof lights[Symbol.iterator] === "function" && !lights[Symbol.iterator]().next().done;
|
|
15764
|
+
if (hasAny) {
|
|
15765
|
+
this._litPipelineWarned = true;
|
|
15766
|
+
console.warn(
|
|
15767
|
+
"melonJS: Light2d normal-map lighting requires the WebGL renderer; the Canvas fallback renders sprites without per-pixel lighting. Switch to `video.WEBGL` or `video.AUTO` to enable the lit pipeline."
|
|
15768
|
+
);
|
|
15769
|
+
}
|
|
15770
|
+
}
|
|
15771
|
+
/**
|
|
15772
|
+
* Render a `Light2d` instance.
|
|
15773
|
+
*
|
|
15774
|
+
* Each renderer implements its own strategy: the WebGL renderer
|
|
15775
|
+
* draws lights as quads through a shared procedural radial-falloff
|
|
15776
|
+
* fragment shader (no per-light texture, color and intensity
|
|
15777
|
+
* encoded in the per-vertex tint so consecutive draws batch); the
|
|
15778
|
+
* Canvas renderer caches a small `Gradient` config object per
|
|
15779
|
+
* light in a `WeakMap` (rebuilt only when the light's radii /
|
|
15780
|
+
* color / intensity change), rasterizes it with `Gradient.toCanvas()`
|
|
15781
|
+
* into a single shared `CanvasRenderTarget`, and composites the
|
|
15782
|
+
* result via `drawImage`. The base implementation is a no-op so
|
|
15783
|
+
* renderers without a lighting path can be polymorphically
|
|
15784
|
+
* substituted.
|
|
15785
|
+
*
|
|
15786
|
+
* Light2d itself is renderer-agnostic — it just calls
|
|
15787
|
+
* `renderer.drawLight(this)` and relies on the renderer to pick
|
|
15788
|
+
* the right machinery.
|
|
15789
|
+
* @param {object} light - the Light2d instance to render
|
|
15790
|
+
* @see Light2d
|
|
15791
|
+
*/
|
|
15792
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
15793
|
+
drawLight(light) {
|
|
15794
|
+
}
|
|
15689
15795
|
/**
|
|
15690
15796
|
* Set the current fill & stroke style color.
|
|
15691
15797
|
* By default, or upon reset, the value is set to #000000.
|
|
@@ -16206,7 +16312,7 @@ var TextureAtlas = class {
|
|
|
16206
16312
|
/**
|
|
16207
16313
|
* @param {object|object[]} atlases - atlas information. See {@link loader.getJSON}
|
|
16208
16314
|
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|CompressedImage|string|OffscreenCanvas[]|HTMLImageElement[]|HTMLCanvasElement[]|string[]} [src=atlas.meta.image] - Image source
|
|
16209
|
-
* @param {boolean} [cache
|
|
16315
|
+
* @param {boolean|object} [options] - either a boolean (legacy `cache` flag — `false` disables `TextureCache` registration; default behavior is to cache) or an options object `{ cache?: boolean, normalMap?: HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap|string }`. When `normalMap` is provided, the atlas exposes a paired normal-map texture sharing the same UVs as the color texture (used by the WebGL renderer's lit pipeline). `HTMLVideoElement` is intentionally not supported as a normal map (would freeze on frame 0 due to per-image GL texture caching).
|
|
16210
16316
|
* @example
|
|
16211
16317
|
* // create a texture atlas from a JSON Object
|
|
16212
16318
|
* game.texture = new me.TextureAtlas(
|
|
@@ -16228,10 +16334,22 @@ var TextureAtlas = class {
|
|
|
16228
16334
|
* anchorPoint : new me.Vector2d(0.5, 0.5)
|
|
16229
16335
|
* },
|
|
16230
16336
|
* me.loader.getImage("spritesheet")
|
|
16337
|
+
* );
|
|
16338
|
+
*
|
|
16339
|
+
* // SpriteIlluminator workflow: pair the color atlas with its normal map
|
|
16340
|
+
* game.texture = new me.TextureAtlas(
|
|
16341
|
+
* me.loader.getJSON("scene"),
|
|
16342
|
+
* me.loader.getImage("scene"),
|
|
16343
|
+
* { normalMap: me.loader.getImage("scene_n") }
|
|
16344
|
+
* );
|
|
16231
16345
|
*/
|
|
16232
|
-
constructor(atlases, src,
|
|
16346
|
+
constructor(atlases, src, options) {
|
|
16347
|
+
const opts = typeof options === "boolean" ? { cache: options } : options || {};
|
|
16348
|
+
const cache2 = opts.cache;
|
|
16349
|
+
const normalMap = opts.normalMap;
|
|
16233
16350
|
this.format = null;
|
|
16234
16351
|
this.sources = /* @__PURE__ */ new Map();
|
|
16352
|
+
this.normalSources = /* @__PURE__ */ new Map();
|
|
16235
16353
|
this.atlases = /* @__PURE__ */ new Map();
|
|
16236
16354
|
this.activeAtlas = void 0;
|
|
16237
16355
|
this._uvCache = { sx: -1, sy: -1, sw: -1, sh: -1, uvs: null };
|
|
@@ -16321,6 +16439,31 @@ var TextureAtlas = class {
|
|
|
16321
16439
|
game.renderer.cache.set(source, this);
|
|
16322
16440
|
});
|
|
16323
16441
|
}
|
|
16442
|
+
if (typeof normalMap !== "undefined" && normalMap !== null) {
|
|
16443
|
+
let resolved;
|
|
16444
|
+
if (typeof normalMap === "string") {
|
|
16445
|
+
resolved = getImage(normalMap);
|
|
16446
|
+
if (!resolved) {
|
|
16447
|
+
throw new Error(
|
|
16448
|
+
"TextureAtlas: normal map image '" + normalMap + "' not found"
|
|
16449
|
+
);
|
|
16450
|
+
}
|
|
16451
|
+
} else if (typeof normalMap === "object" && typeof normalMap.width === "number" && typeof normalMap.height === "number") {
|
|
16452
|
+
if (typeof normalMap.videoWidth === "number") {
|
|
16453
|
+
throw new TypeError(
|
|
16454
|
+
"TextureAtlas: options.normalMap does not support HTMLVideoElement (the lit pipeline caches the texture per image reference and would freeze on frame 0)"
|
|
16455
|
+
);
|
|
16456
|
+
}
|
|
16457
|
+
resolved = normalMap;
|
|
16458
|
+
} else {
|
|
16459
|
+
throw new TypeError(
|
|
16460
|
+
"TextureAtlas: options.normalMap must be an image-like, a loader key string, or null/undefined; got " + typeof normalMap
|
|
16461
|
+
);
|
|
16462
|
+
}
|
|
16463
|
+
this.sources.forEach((_source, key) => {
|
|
16464
|
+
this.normalSources.set(key, resolved);
|
|
16465
|
+
});
|
|
16466
|
+
}
|
|
16324
16467
|
}
|
|
16325
16468
|
/**
|
|
16326
16469
|
* return the default or specified atlas dictionnary
|
|
@@ -16353,6 +16496,22 @@ var TextureAtlas = class {
|
|
|
16353
16496
|
return this.sources.get(this.activeAtlas);
|
|
16354
16497
|
}
|
|
16355
16498
|
}
|
|
16499
|
+
/**
|
|
16500
|
+
* Return the paired normal-map texture for the given region, or `null`
|
|
16501
|
+
* if no normal map was provided to this atlas. The normal map shares
|
|
16502
|
+
* the same UV layout as the color texture returned by {@link TextureAtlas#getTexture}.
|
|
16503
|
+
* @param {object} [region] - region name in case of multipack textures
|
|
16504
|
+
* @returns {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap|null}
|
|
16505
|
+
*/
|
|
16506
|
+
getNormalTexture(region) {
|
|
16507
|
+
if (this.normalSources.size === 0) {
|
|
16508
|
+
return null;
|
|
16509
|
+
}
|
|
16510
|
+
if (typeof region === "object" && typeof region.texture === "string") {
|
|
16511
|
+
return this.normalSources.get(region.texture) ?? null;
|
|
16512
|
+
}
|
|
16513
|
+
return this.normalSources.get(this.activeAtlas) ?? null;
|
|
16514
|
+
}
|
|
16356
16515
|
/**
|
|
16357
16516
|
* add a region to the atlas
|
|
16358
16517
|
* @param {string} name - region mame
|
|
@@ -16851,6 +17010,7 @@ var CanvasRenderer = class extends Renderer {
|
|
|
16851
17010
|
reset() {
|
|
16852
17011
|
super.reset();
|
|
16853
17012
|
this.clearColor(this.currentColor, this.settings.transparent !== true);
|
|
17013
|
+
this._lightCache = void 0;
|
|
16854
17014
|
}
|
|
16855
17015
|
/**
|
|
16856
17016
|
* Reset the canvas transform to identity
|
|
@@ -17044,6 +17204,59 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17044
17204
|
}
|
|
17045
17205
|
context.drawImage(source, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
17046
17206
|
}
|
|
17207
|
+
/**
|
|
17208
|
+
* @inheritdoc
|
|
17209
|
+
*
|
|
17210
|
+
* Renders the light by drawing a cached radial `Gradient` via
|
|
17211
|
+
* `Gradient.toCanvas()`. The Gradient instance is cached per-Light2d
|
|
17212
|
+
* in a `WeakMap` and rebuilt only when the light's radii / color /
|
|
17213
|
+
* intensity change. `toCanvas` itself shares a single
|
|
17214
|
+
* `CanvasRenderTarget` across all gradients in the engine, so memory
|
|
17215
|
+
* stays at O(1) regardless of how many lights are active.
|
|
17216
|
+
*
|
|
17217
|
+
* The cached Gradient is always circular (outer radius =
|
|
17218
|
+
* `max(radiusX, radiusY)`) — `drawImage`'s non-uniform stretch
|
|
17219
|
+
* produces the elliptical falloff for non-square lights, matching
|
|
17220
|
+
* the procedural shader's behavior on WebGL.
|
|
17221
|
+
* @param {object} light - the Light2d instance to render
|
|
17222
|
+
*/
|
|
17223
|
+
drawLight(light) {
|
|
17224
|
+
if (this._lightCache === void 0) {
|
|
17225
|
+
this._lightCache = /* @__PURE__ */ new WeakMap();
|
|
17226
|
+
}
|
|
17227
|
+
let entry = this._lightCache.get(light);
|
|
17228
|
+
const c = light.color;
|
|
17229
|
+
if (entry === void 0 || entry.radiusX !== light.radiusX || entry.radiusY !== light.radiusY || entry.r !== c.r || entry.g !== c.g || entry.b !== c.b || entry.intensity !== light.intensity) {
|
|
17230
|
+
const r = Math.max(light.radiusX, light.radiusY);
|
|
17231
|
+
const gradient = this.createRadialGradient(r, r, 0, r, r, r);
|
|
17232
|
+
gradient.addColorStop(0, c.toRGBA(light.intensity));
|
|
17233
|
+
gradient.addColorStop(1, c.toRGBA(0));
|
|
17234
|
+
entry = {
|
|
17235
|
+
gradient,
|
|
17236
|
+
radius: r,
|
|
17237
|
+
radiusX: light.radiusX,
|
|
17238
|
+
radiusY: light.radiusY,
|
|
17239
|
+
r: c.r,
|
|
17240
|
+
g: c.g,
|
|
17241
|
+
b: c.b,
|
|
17242
|
+
intensity: light.intensity
|
|
17243
|
+
};
|
|
17244
|
+
this._lightCache.set(light, entry);
|
|
17245
|
+
}
|
|
17246
|
+
const r2 = entry.radius * 2;
|
|
17247
|
+
const canvas = entry.gradient.toCanvas(this, 0, 0, r2, r2);
|
|
17248
|
+
this.drawImage(
|
|
17249
|
+
canvas,
|
|
17250
|
+
0,
|
|
17251
|
+
0,
|
|
17252
|
+
r2,
|
|
17253
|
+
r2,
|
|
17254
|
+
light.pos.x,
|
|
17255
|
+
light.pos.y,
|
|
17256
|
+
light.width,
|
|
17257
|
+
light.height
|
|
17258
|
+
);
|
|
17259
|
+
}
|
|
17047
17260
|
/**
|
|
17048
17261
|
* Draw a pattern within the given rectangle.
|
|
17049
17262
|
* @param {CanvasPattern} pattern - Pattern object
|
|
@@ -17778,14 +17991,15 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17778
17991
|
if (typeof mask !== "undefined") {
|
|
17779
17992
|
context.beginPath();
|
|
17780
17993
|
}
|
|
17994
|
+
this._maskInvertOuterAdded = false;
|
|
17781
17995
|
}
|
|
17782
17996
|
if (typeof mask !== "undefined") {
|
|
17783
17997
|
switch (mask.type) {
|
|
17784
17998
|
// RoundRect
|
|
17785
17999
|
case "RoundRect":
|
|
17786
18000
|
context.roundRect(
|
|
17787
|
-
mask.top,
|
|
17788
18001
|
mask.left,
|
|
18002
|
+
mask.top,
|
|
17789
18003
|
mask.width,
|
|
17790
18004
|
mask.height,
|
|
17791
18005
|
mask.radius
|
|
@@ -17794,7 +18008,7 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17794
18008
|
// Rect or Bounds
|
|
17795
18009
|
case "Rectangle":
|
|
17796
18010
|
case "Bounds":
|
|
17797
|
-
context.rect(mask.
|
|
18011
|
+
context.rect(mask.left, mask.top, mask.width, mask.height);
|
|
17798
18012
|
break;
|
|
17799
18013
|
// Polygon or Line
|
|
17800
18014
|
case "Polygon":
|
|
@@ -17837,7 +18051,10 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17837
18051
|
}
|
|
17838
18052
|
this.maskLevel++;
|
|
17839
18053
|
if (invert === true) {
|
|
17840
|
-
|
|
18054
|
+
if (this._maskInvertOuterAdded !== true) {
|
|
18055
|
+
context.rect(0, 0, this.getCanvas().width, this.getCanvas().height);
|
|
18056
|
+
this._maskInvertOuterAdded = true;
|
|
18057
|
+
}
|
|
17841
18058
|
context.clip("evenodd");
|
|
17842
18059
|
} else {
|
|
17843
18060
|
context.clip();
|
|
@@ -21354,6 +21571,7 @@ var Sprite = class extends Renderable {
|
|
|
21354
21571
|
* @param {number} [settings.flipX] - flip the sprite on the horizontal axis
|
|
21355
21572
|
* @param {number} [settings.flipY] - flip the sprite on the vertical axis
|
|
21356
21573
|
* @param {Vector2d} [settings.anchorPoint={x:0.5, y:0.5}] - Anchor point to draw the frame at (defaults to the center of the frame).
|
|
21574
|
+
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap|string} [settings.normalMap] - optional normal-map texture used for per-pixel lighting (SpriteIlluminator-style). Same layout/UVs as `settings.image`. When omitted (default), the sprite renders unlit and pays no extra cost. Ignored by the Canvas renderer. Note: `HTMLVideoElement` is intentionally not supported — normal maps encode static surface directions in RGB, and the engine caches the GL texture per image reference (a video would freeze on frame 0).
|
|
21357
21575
|
* @example
|
|
21358
21576
|
* // create a single sprite from a standalone image, with anchor in the center
|
|
21359
21577
|
* let sprite = new me.Sprite(0, 0, {
|
|
@@ -21390,6 +21608,7 @@ var Sprite = class extends Renderable {
|
|
|
21390
21608
|
this.offset = vector2dPool.get(0, 0);
|
|
21391
21609
|
this.isVideo = false;
|
|
21392
21610
|
this.source = null;
|
|
21611
|
+
this._normalMap = null;
|
|
21393
21612
|
this.anim = {};
|
|
21394
21613
|
this.resetAnim = void 0;
|
|
21395
21614
|
this.current = {
|
|
@@ -21463,6 +21682,25 @@ var Sprite = class extends Renderable {
|
|
|
21463
21682
|
this.textureAtlas = this.source.getAtlas();
|
|
21464
21683
|
}
|
|
21465
21684
|
}
|
|
21685
|
+
if (settings.image instanceof TextureAtlas && typeof settings.image.getNormalTexture === "function") {
|
|
21686
|
+
const fromAtlas = settings.image.getNormalTexture();
|
|
21687
|
+
if (fromAtlas) {
|
|
21688
|
+
this.normalMap = fromAtlas;
|
|
21689
|
+
}
|
|
21690
|
+
}
|
|
21691
|
+
if (this.normalMap === null && typeof settings.normalMap !== "undefined" && settings.normalMap !== null) {
|
|
21692
|
+
if (typeof settings.normalMap === "string") {
|
|
21693
|
+
const resolved = getImage(settings.normalMap);
|
|
21694
|
+
if (!resolved) {
|
|
21695
|
+
throw new Error(
|
|
21696
|
+
"me.Sprite: '" + settings.normalMap + "' normal map image not found!"
|
|
21697
|
+
);
|
|
21698
|
+
}
|
|
21699
|
+
this.normalMap = resolved;
|
|
21700
|
+
} else {
|
|
21701
|
+
this.normalMap = settings.normalMap;
|
|
21702
|
+
}
|
|
21703
|
+
}
|
|
21466
21704
|
if (typeof settings.atlas !== "undefined") {
|
|
21467
21705
|
this.textureAtlas = settings.atlas;
|
|
21468
21706
|
this.atlasIndices = settings.atlasIndices;
|
|
@@ -21505,6 +21743,37 @@ var Sprite = class extends Renderable {
|
|
|
21505
21743
|
this.setCurrentAnimation("default");
|
|
21506
21744
|
}
|
|
21507
21745
|
}
|
|
21746
|
+
/**
|
|
21747
|
+
* The optional normal-map image paired with this sprite's color
|
|
21748
|
+
* texture (SpriteIlluminator workflow). When set, the WebGL
|
|
21749
|
+
* renderer's lit pipeline samples this texture for per-pixel
|
|
21750
|
+
* lighting using `Stage._activeLights`. `null` when unlit.
|
|
21751
|
+
* Setting any non-image value (or anything without numeric
|
|
21752
|
+
* `width`/`height`) throws — assign `null` to clear.
|
|
21753
|
+
*
|
|
21754
|
+
* Silently ignored by the Canvas renderer.
|
|
21755
|
+
* @type {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap|null}
|
|
21756
|
+
*/
|
|
21757
|
+
get normalMap() {
|
|
21758
|
+
return this._normalMap;
|
|
21759
|
+
}
|
|
21760
|
+
set normalMap(value) {
|
|
21761
|
+
if (value === null || value === void 0) {
|
|
21762
|
+
this._normalMap = null;
|
|
21763
|
+
return;
|
|
21764
|
+
}
|
|
21765
|
+
if (typeof value !== "object" || typeof value.width !== "number" || typeof value.height !== "number") {
|
|
21766
|
+
throw new TypeError(
|
|
21767
|
+
"Sprite.normalMap must be null or an image-like object with numeric width/height (HTMLImageElement, HTMLCanvasElement, OffscreenCanvas, ImageBitmap)"
|
|
21768
|
+
);
|
|
21769
|
+
}
|
|
21770
|
+
if (typeof value.videoWidth === "number") {
|
|
21771
|
+
throw new TypeError(
|
|
21772
|
+
"Sprite.normalMap does not support HTMLVideoElement (the lit pipeline caches the texture per image reference and would freeze on frame 0)"
|
|
21773
|
+
);
|
|
21774
|
+
}
|
|
21775
|
+
this._normalMap = value;
|
|
21776
|
+
}
|
|
21508
21777
|
/**
|
|
21509
21778
|
* return the flickering state of the object
|
|
21510
21779
|
* @returns {boolean}
|
|
@@ -21854,9 +22123,32 @@ var Sprite = class extends Renderable {
|
|
|
21854
22123
|
}
|
|
21855
22124
|
return super.update(dt);
|
|
21856
22125
|
}
|
|
22126
|
+
/**
|
|
22127
|
+
* Prepare the rendering context before drawing this sprite (automatically called by melonJS).
|
|
22128
|
+
* Extends `Renderable.preDraw` to publish this sprite's `normalMap` (if any)
|
|
22129
|
+
* on the renderer so the WebGL lit pipeline can pair it with the next
|
|
22130
|
+
* `drawImage` call. Cleared back in `postDraw`.
|
|
22131
|
+
* @param {Renderer} renderer - a renderer instance
|
|
22132
|
+
*/
|
|
22133
|
+
preDraw(renderer2) {
|
|
22134
|
+
super.preDraw(renderer2);
|
|
22135
|
+
if (this._normalMap !== null) {
|
|
22136
|
+
renderer2.currentNormalMap = this._normalMap;
|
|
22137
|
+
}
|
|
22138
|
+
}
|
|
22139
|
+
/**
|
|
22140
|
+
* restore the rendering context after drawing this sprite (automatically called by melonJS).
|
|
22141
|
+
* @param {Renderer} renderer - a renderer instance
|
|
22142
|
+
*/
|
|
22143
|
+
postDraw(renderer2) {
|
|
22144
|
+
if (this._normalMap !== null) {
|
|
22145
|
+
renderer2.currentNormalMap = null;
|
|
22146
|
+
}
|
|
22147
|
+
super.postDraw(renderer2);
|
|
22148
|
+
}
|
|
21857
22149
|
/**
|
|
21858
22150
|
* draw this sprite (automatically called by melonJS)
|
|
21859
|
-
* @param {
|
|
22151
|
+
* @param {Renderer} renderer - a renderer instance
|
|
21860
22152
|
* @param {Camera2d} [viewport] - the viewport to (re)draw
|
|
21861
22153
|
*/
|
|
21862
22154
|
draw(renderer2) {
|
|
@@ -21915,6 +22207,7 @@ var Sprite = class extends Renderable {
|
|
|
21915
22207
|
this.image.currentTime = 0;
|
|
21916
22208
|
}
|
|
21917
22209
|
this.image = void 0;
|
|
22210
|
+
this._normalMap = null;
|
|
21918
22211
|
super.destroy();
|
|
21919
22212
|
}
|
|
21920
22213
|
};
|
|
@@ -22343,8 +22636,6 @@ var GLShader = class {
|
|
|
22343
22636
|
stride,
|
|
22344
22637
|
element.offset
|
|
22345
22638
|
);
|
|
22346
|
-
} else {
|
|
22347
|
-
gl.disableVertexAttribArray(index);
|
|
22348
22639
|
}
|
|
22349
22640
|
}
|
|
22350
22641
|
}
|
|
@@ -23308,6 +23599,13 @@ var Camera2d = class extends Renderable {
|
|
|
23308
23599
|
} else {
|
|
23309
23600
|
renderer2.setProjection(this.projectionMatrix);
|
|
23310
23601
|
}
|
|
23602
|
+
const stage = state_default.current();
|
|
23603
|
+
renderer2.setLightUniforms(
|
|
23604
|
+
stage?._activeLights,
|
|
23605
|
+
stage?.ambientLightingColor,
|
|
23606
|
+
translateX,
|
|
23607
|
+
translateY
|
|
23608
|
+
);
|
|
23311
23609
|
container.preDraw(r);
|
|
23312
23610
|
if (isNonDefault) {
|
|
23313
23611
|
const view = this.worldView;
|
|
@@ -23324,6 +23622,9 @@ var Camera2d = class extends Renderable {
|
|
|
23324
23622
|
this.drawFX(renderer2);
|
|
23325
23623
|
container.postDraw(r);
|
|
23326
23624
|
this.postDraw(r);
|
|
23625
|
+
if (stage) {
|
|
23626
|
+
stage.drawLighting(renderer2, this, translateX, translateY);
|
|
23627
|
+
}
|
|
23327
23628
|
r.endPostEffect(this);
|
|
23328
23629
|
if (this._colorMatrixEffect) {
|
|
23329
23630
|
const idx = this.postEffects.indexOf(this._colorMatrixEffect);
|
|
@@ -23361,28 +23662,50 @@ var Stage = class {
|
|
|
23361
23662
|
cameras;
|
|
23362
23663
|
/**
|
|
23363
23664
|
* The list of active lights in this stage.
|
|
23364
|
-
*
|
|
23665
|
+
*
|
|
23666
|
+
* Since 19.3.0, `Light2d` is a first-class world Renderable — the
|
|
23667
|
+
* recommended pattern is to add lights directly to `app.world` (or any
|
|
23668
|
+
* container, including a sprite, so the light follows it via parent
|
|
23669
|
+
* transforms). The `lights` Map remains for backward compatibility:
|
|
23670
|
+
* any entry added via `this.lights.set(name, light)` in
|
|
23671
|
+
* `onResetEvent()` is automatically adopted into the world tree at
|
|
23672
|
+
* stage reset time so it renders normally.
|
|
23365
23673
|
* @see Light2d
|
|
23366
23674
|
* @see Stage.ambientLight
|
|
23367
23675
|
* @example
|
|
23368
|
-
* //
|
|
23369
|
-
* const whiteLight = new Light2d(
|
|
23370
|
-
*
|
|
23676
|
+
* // recommended:
|
|
23677
|
+
* const whiteLight = new Light2d(100, 100, 140, 140, "#fff", 0.7);
|
|
23678
|
+
* app.world.addChild(whiteLight);
|
|
23679
|
+
*
|
|
23680
|
+
* // legacy (still works, auto-adopted into world):
|
|
23371
23681
|
* this.lights.set("whiteLight", whiteLight);
|
|
23372
|
-
*
|
|
23682
|
+
*
|
|
23373
23683
|
* this.ambientLight.parseCSS("#1117");
|
|
23374
|
-
* // make the light follow the mouse
|
|
23375
|
-
* input.registerPointerEvent("pointermove", app.viewport, (event) => {
|
|
23376
|
-
* whiteLight.centerOn(event.gameX, event.gameY);
|
|
23377
|
-
* });
|
|
23378
23684
|
*/
|
|
23379
23685
|
lights;
|
|
23686
|
+
/**
|
|
23687
|
+
* Internal set of active lights, auto-populated by `Light2d`'s
|
|
23688
|
+
* `onActivateEvent` / `onDeactivateEvent` hooks. Used by Camera2d's
|
|
23689
|
+
* ambient-overlay pass to compute the cutouts.
|
|
23690
|
+
* @ignore
|
|
23691
|
+
*/
|
|
23692
|
+
_activeLights;
|
|
23380
23693
|
/**
|
|
23381
23694
|
* an ambient light that will be added to the stage rendering
|
|
23382
23695
|
* @default "#000000"
|
|
23383
23696
|
* @see Light2d
|
|
23384
23697
|
*/
|
|
23385
23698
|
ambientLight;
|
|
23699
|
+
/**
|
|
23700
|
+
* Base light level applied to every normal-mapped sprite in the
|
|
23701
|
+
* lit rendering path. Unlike {@link Stage#ambientLight} (which is
|
|
23702
|
+
* the dark overlay punched by each light's cutout), this color is
|
|
23703
|
+
* added to every lit pixel so unlit areas don't render pure
|
|
23704
|
+
* black. Defaults to black (0, 0, 0) — sprites without a
|
|
23705
|
+
* `normalMap` ignore it entirely.
|
|
23706
|
+
* @default "#000000"
|
|
23707
|
+
*/
|
|
23708
|
+
ambientLightingColor;
|
|
23386
23709
|
/**
|
|
23387
23710
|
* The given constructor options
|
|
23388
23711
|
*/
|
|
@@ -23396,9 +23719,26 @@ var Stage = class {
|
|
|
23396
23719
|
constructor(settings) {
|
|
23397
23720
|
this.cameras = /* @__PURE__ */ new Map();
|
|
23398
23721
|
this.lights = /* @__PURE__ */ new Map();
|
|
23722
|
+
this._activeLights = /* @__PURE__ */ new Set();
|
|
23399
23723
|
this.ambientLight = new Color(0, 0, 0, 0);
|
|
23724
|
+
this.ambientLightingColor = new Color(0, 0, 0, 1);
|
|
23400
23725
|
this.settings = Object.assign({}, default_settings, settings || {});
|
|
23401
23726
|
}
|
|
23727
|
+
/**
|
|
23728
|
+
* Called by `Light2d.onActivateEvent` to register the light with the
|
|
23729
|
+
* stage's ambient-overlay cutout list. Users normally don't call this.
|
|
23730
|
+
* @ignore
|
|
23731
|
+
*/
|
|
23732
|
+
_registerLight(light) {
|
|
23733
|
+
this._activeLights.add(light);
|
|
23734
|
+
}
|
|
23735
|
+
/**
|
|
23736
|
+
* Called by `Light2d.onDeactivateEvent` to deregister the light.
|
|
23737
|
+
* @ignore
|
|
23738
|
+
*/
|
|
23739
|
+
_unregisterLight(light) {
|
|
23740
|
+
this._activeLights.delete(light);
|
|
23741
|
+
}
|
|
23402
23742
|
/**
|
|
23403
23743
|
* Object reset function
|
|
23404
23744
|
* @ignore
|
|
@@ -23419,6 +23759,13 @@ var Stage = class {
|
|
|
23419
23759
|
}
|
|
23420
23760
|
emit(STAGE_RESET, this);
|
|
23421
23761
|
this.onResetEvent(app, ...extraArgs);
|
|
23762
|
+
if (app && app.world) {
|
|
23763
|
+
this.lights.forEach((light) => {
|
|
23764
|
+
if (!light.ancestor) {
|
|
23765
|
+
app.world.addChild(light);
|
|
23766
|
+
}
|
|
23767
|
+
});
|
|
23768
|
+
}
|
|
23422
23769
|
}
|
|
23423
23770
|
/**
|
|
23424
23771
|
* update function
|
|
@@ -23433,40 +23780,60 @@ var Stage = class {
|
|
|
23433
23780
|
isDirty = true;
|
|
23434
23781
|
}
|
|
23435
23782
|
});
|
|
23436
|
-
this.lights.forEach((light) => {
|
|
23437
|
-
if (light.update()) {
|
|
23438
|
-
isDirty = true;
|
|
23439
|
-
}
|
|
23440
|
-
});
|
|
23441
23783
|
return isDirty;
|
|
23442
23784
|
}
|
|
23443
23785
|
/**
|
|
23444
23786
|
* draw the current stage
|
|
23787
|
+
*
|
|
23788
|
+
* Lights are rendered as part of the world tree (they're now first-class
|
|
23789
|
+
* Renderables) and the ambient overlay pass runs inside each Camera's
|
|
23790
|
+
* post-effect FBO bracket via {@link Stage#drawLighting}.
|
|
23445
23791
|
* @ignore
|
|
23446
23792
|
* @param renderer - the renderer object to draw with
|
|
23447
23793
|
* @param world - the world object to draw
|
|
23448
23794
|
*/
|
|
23449
23795
|
draw(renderer2, world) {
|
|
23450
|
-
const r = renderer2;
|
|
23451
23796
|
this.cameras.forEach((camera) => {
|
|
23452
23797
|
camera.draw(renderer2, world);
|
|
23453
|
-
if (this.ambientLight.alpha !== 0) {
|
|
23454
|
-
r.save();
|
|
23455
|
-
this.lights.forEach((light) => {
|
|
23456
|
-
r.setMask(light.getVisibleArea(), true);
|
|
23457
|
-
});
|
|
23458
|
-
r.setColor(this.ambientLight);
|
|
23459
|
-
r.fillRect(0, 0, camera.width, camera.height);
|
|
23460
|
-
r.clearMask();
|
|
23461
|
-
r.restore();
|
|
23462
|
-
}
|
|
23463
|
-
this.lights.forEach((light) => {
|
|
23464
|
-
light.preDraw(r);
|
|
23465
|
-
light.draw(r);
|
|
23466
|
-
light.postDraw(r);
|
|
23467
|
-
});
|
|
23468
23798
|
});
|
|
23469
23799
|
}
|
|
23800
|
+
/**
|
|
23801
|
+
* Draw the stage's ambient-light overlay with cutouts for each active
|
|
23802
|
+
* light. Called from each `Camera2d` inside its post-effect FBO bracket —
|
|
23803
|
+
* lights themselves render via the world tree (they're standard
|
|
23804
|
+
* Renderables); this pass only paints the dark fill that the lights cut
|
|
23805
|
+
* holes through.
|
|
23806
|
+
*
|
|
23807
|
+
* Subclasses can override this method to implement custom lighting (e.g.
|
|
23808
|
+
* per-pixel normal-mapped lighting via a custom shader). Called once per
|
|
23809
|
+
* camera per frame.
|
|
23810
|
+
* @param renderer - the active renderer
|
|
23811
|
+
* @param camera - the camera currently rendering this stage
|
|
23812
|
+
* @param translateX - the same world-to-screen X translate that
|
|
23813
|
+
* `Camera2d.draw()` applies to the world container (i.e.
|
|
23814
|
+
* `camera.pos.x + camera.offset.x` for the default camera, plus
|
|
23815
|
+
* the container's own offset for non-default cameras)
|
|
23816
|
+
* @param translateY - the world-to-screen Y translate (see `translateX`)
|
|
23817
|
+
*/
|
|
23818
|
+
drawLighting(renderer2, camera, translateX = camera.pos.x + camera.offset.x, translateY = camera.pos.y + camera.offset.y) {
|
|
23819
|
+
if (this.ambientLight.alpha === 0) {
|
|
23820
|
+
return;
|
|
23821
|
+
}
|
|
23822
|
+
const r = renderer2;
|
|
23823
|
+
r.save();
|
|
23824
|
+
const tx = translateX;
|
|
23825
|
+
const ty = translateY;
|
|
23826
|
+
if (tx !== 0 || ty !== 0) {
|
|
23827
|
+
r.translate(-tx, -ty);
|
|
23828
|
+
}
|
|
23829
|
+
this._activeLights.forEach((light) => {
|
|
23830
|
+
r.setMask(light.getVisibleArea(), true);
|
|
23831
|
+
});
|
|
23832
|
+
r.setColor(this.ambientLight);
|
|
23833
|
+
r.fillRect(tx, ty, camera.width, camera.height);
|
|
23834
|
+
r.clearMask();
|
|
23835
|
+
r.restore();
|
|
23836
|
+
}
|
|
23470
23837
|
/**
|
|
23471
23838
|
* destroy function
|
|
23472
23839
|
* @ignore
|
|
@@ -23474,9 +23841,12 @@ var Stage = class {
|
|
|
23474
23841
|
destroy(app) {
|
|
23475
23842
|
this.cameras.clear();
|
|
23476
23843
|
this.lights.forEach((light) => {
|
|
23477
|
-
light.
|
|
23844
|
+
if (!light.ancestor) {
|
|
23845
|
+
light.destroy();
|
|
23846
|
+
}
|
|
23478
23847
|
});
|
|
23479
23848
|
this.lights.clear();
|
|
23849
|
+
this._activeLights.clear();
|
|
23480
23850
|
this.onDestroyEvent(app);
|
|
23481
23851
|
}
|
|
23482
23852
|
/**
|
|
@@ -26825,64 +27195,33 @@ var ImageLayer = class extends Sprite {
|
|
|
26825
27195
|
};
|
|
26826
27196
|
|
|
26827
27197
|
// src/renderable/light2d.js
|
|
26828
|
-
function createGradient(light) {
|
|
26829
|
-
const context = light.texture.context;
|
|
26830
|
-
const x1 = light.texture.width / 2;
|
|
26831
|
-
const y1 = light.texture.height / 2;
|
|
26832
|
-
const radiusX = light.radiusX;
|
|
26833
|
-
const radiusY = light.radiusY;
|
|
26834
|
-
let scaleX;
|
|
26835
|
-
let scaleY;
|
|
26836
|
-
let invScaleX;
|
|
26837
|
-
let invScaleY;
|
|
26838
|
-
let gradient;
|
|
26839
|
-
light.texture.clear();
|
|
26840
|
-
if (radiusX >= radiusY) {
|
|
26841
|
-
scaleX = 1;
|
|
26842
|
-
invScaleX = 1;
|
|
26843
|
-
scaleY = radiusY / radiusX;
|
|
26844
|
-
invScaleY = radiusX / radiusY;
|
|
26845
|
-
gradient = context.createRadialGradient(
|
|
26846
|
-
x1,
|
|
26847
|
-
y1 * invScaleY,
|
|
26848
|
-
0,
|
|
26849
|
-
x1,
|
|
26850
|
-
radiusY * invScaleY,
|
|
26851
|
-
radiusX
|
|
26852
|
-
);
|
|
26853
|
-
} else {
|
|
26854
|
-
scaleY = 1;
|
|
26855
|
-
invScaleY = 1;
|
|
26856
|
-
scaleX = radiusX / radiusY;
|
|
26857
|
-
invScaleX = radiusY / radiusX;
|
|
26858
|
-
gradient = context.createRadialGradient(
|
|
26859
|
-
x1 * invScaleX,
|
|
26860
|
-
y1,
|
|
26861
|
-
0,
|
|
26862
|
-
x1 * invScaleX,
|
|
26863
|
-
y1,
|
|
26864
|
-
radiusY
|
|
26865
|
-
);
|
|
26866
|
-
}
|
|
26867
|
-
gradient.addColorStop(0, light.color.toRGBA(light.intensity));
|
|
26868
|
-
gradient.addColorStop(1, light.color.toRGBA(0));
|
|
26869
|
-
context.fillStyle = gradient;
|
|
26870
|
-
context.setTransform(scaleX, 0, 0, scaleY, 0, 0);
|
|
26871
|
-
context.fillRect(
|
|
26872
|
-
0,
|
|
26873
|
-
0,
|
|
26874
|
-
light.texture.width * invScaleX,
|
|
26875
|
-
light.texture.height * invScaleY
|
|
26876
|
-
);
|
|
26877
|
-
}
|
|
26878
27198
|
var Light2d = class extends Renderable {
|
|
26879
27199
|
/**
|
|
26880
|
-
*
|
|
26881
|
-
*
|
|
27200
|
+
* Create a 2D point light.
|
|
27201
|
+
*
|
|
27202
|
+
* A `Light2d` is a first-class world Renderable: add it to a container
|
|
27203
|
+
* with `app.world.addChild(light)` (or any sub-container, including a
|
|
27204
|
+
* `Sprite`, so the light follows the parent via its transform). On
|
|
27205
|
+
* activation, the light auto-registers with the active `Stage`'s
|
|
27206
|
+
* lighting set so the ambient overlay (`Stage.ambientLight`) cuts a
|
|
27207
|
+
* hole at the light's visible area, and a radial gradient from the
|
|
27208
|
+
* given `color` (full intensity at center → fully transparent at the
|
|
27209
|
+
* radius) is composited additively on top — producing a soft spot
|
|
27210
|
+
* light. Rendering happens inside each `Camera2d`'s post-effect FBO
|
|
27211
|
+
* bracket so any camera shader (vignette, color-matrix, scanlines,
|
|
27212
|
+
* etc.) wraps the lighting output.
|
|
27213
|
+
*
|
|
27214
|
+
* Set `radiusY` to a different value than `radiusX` for a stretched
|
|
27215
|
+
* (elliptical) light. The `intensity` parameter scales the gradient's
|
|
27216
|
+
* inner alpha; the `Stage.ambientLight` color and alpha control how
|
|
27217
|
+
* dark the unlit areas are. Use `light.blendMode` to override the
|
|
27218
|
+
* default additive blend if needed.
|
|
27219
|
+
* @param {number} x - The horizontal position of the light's center (matches `Ellipse(x, y, w, h)` conventions).
|
|
27220
|
+
* @param {number} y - The vertical position of the light's center.
|
|
26882
27221
|
* @param {number} radiusX - The horizontal radius of the light.
|
|
26883
27222
|
* @param {number} [radiusY=radiusX] - The vertical radius of the light.
|
|
26884
|
-
* @param {Color|string} [color="#FFF"] -
|
|
26885
|
-
* @param {number} [intensity=0.7] - The
|
|
27223
|
+
* @param {Color|string} [color="#FFF"] - The color of the light at full intensity.
|
|
27224
|
+
* @param {number} [intensity=0.7] - The peak alpha of the radial gradient at the light's center (0–1).
|
|
26886
27225
|
*/
|
|
26887
27226
|
constructor(x, y, radiusX, radiusY = radiusX, color = "#FFF", intensity = 0.7) {
|
|
26888
27227
|
super(x, y, radiusX * 2, radiusY * 2);
|
|
@@ -26892,32 +27231,77 @@ var Light2d = class extends Renderable {
|
|
|
26892
27231
|
this.intensity = intensity;
|
|
26893
27232
|
this.blendMode = "lighter";
|
|
26894
27233
|
this.visibleArea = ellipsePool.get(
|
|
26895
|
-
this.
|
|
26896
|
-
this.
|
|
27234
|
+
this.pos.x,
|
|
27235
|
+
this.pos.y,
|
|
26897
27236
|
this.width,
|
|
26898
27237
|
this.height
|
|
26899
27238
|
);
|
|
26900
|
-
this.
|
|
26901
|
-
|
|
26902
|
-
|
|
26903
|
-
this.anchorPoint.set(0, 0);
|
|
26904
|
-
createGradient(this);
|
|
27239
|
+
this.anchorPoint.set(0.5, 0.5);
|
|
27240
|
+
this.illuminationOnly = false;
|
|
27241
|
+
this.lightHeight = Math.max(radiusX, radiusY) * 0.075;
|
|
26905
27242
|
}
|
|
26906
27243
|
/**
|
|
26907
|
-
*
|
|
27244
|
+
* the horizontal coordinate of this light's center.
|
|
27245
|
+
* Overrides Rect's getter, which assumes `pos` is the bbox top-left and
|
|
27246
|
+
* returns `pos.x + width/2`. Light2d uses `anchorPoint = (0.5, 0.5)`, so
|
|
27247
|
+
* `pos` already IS the center.
|
|
27248
|
+
* @type {number}
|
|
27249
|
+
*/
|
|
27250
|
+
get centerX() {
|
|
27251
|
+
return this.pos.x;
|
|
27252
|
+
}
|
|
27253
|
+
set centerX(value) {
|
|
27254
|
+
this.pos.x = value;
|
|
27255
|
+
this.recalc();
|
|
27256
|
+
this.updateBounds();
|
|
27257
|
+
}
|
|
27258
|
+
/**
|
|
27259
|
+
* the vertical coordinate of this light's center.
|
|
27260
|
+
* @see Light2d#centerX
|
|
27261
|
+
* @type {number}
|
|
27262
|
+
*/
|
|
27263
|
+
get centerY() {
|
|
27264
|
+
return this.pos.y;
|
|
27265
|
+
}
|
|
27266
|
+
set centerY(value) {
|
|
27267
|
+
this.pos.y = value;
|
|
27268
|
+
this.recalc();
|
|
27269
|
+
this.updateBounds();
|
|
27270
|
+
}
|
|
27271
|
+
/**
|
|
27272
|
+
* Set new radii for this light.
|
|
27273
|
+
*
|
|
27274
|
+
* Updates `radiusX`/`radiusY` and the underlying bbox (via
|
|
27275
|
+
* `Renderable.resize(width, height)`) so `getBounds()` and
|
|
27276
|
+
* `getVisibleArea()` — which feed the ambient-cutout pass — track the
|
|
27277
|
+
* new size. The Canvas renderer's gradient cache auto-invalidates on
|
|
27278
|
+
* next draw via its property comparison; the WebGL procedural shader
|
|
27279
|
+
* adapts to the new dimensions automatically.
|
|
27280
|
+
*
|
|
27281
|
+
* Named `setRadii` (not `resize`) so it does not shadow
|
|
27282
|
+
* `Renderable.resize(width, height)` — code that operates on a
|
|
27283
|
+
* generic `Renderable` and calls `.resize(w, h)` keeps working when
|
|
27284
|
+
* the instance happens to be a `Light2d`.
|
|
27285
|
+
* @param {number} radiusX - new horizontal radius
|
|
27286
|
+
* @param {number} [radiusY=radiusX] - new vertical radius
|
|
27287
|
+
*/
|
|
27288
|
+
setRadii(radiusX, radiusY = radiusX) {
|
|
27289
|
+
this.radiusX = radiusX;
|
|
27290
|
+
this.radiusY = radiusY;
|
|
27291
|
+
this.resize(radiusX * 2, radiusY * 2);
|
|
27292
|
+
}
|
|
27293
|
+
/**
|
|
27294
|
+
* returns a geometry representing the visible area of this light, in
|
|
27295
|
+
* world-space coordinates (so it aligns with the rendered gradient
|
|
27296
|
+
* regardless of camera scroll or container parenting).
|
|
26908
27297
|
* @returns {Ellipse} the light visible mask
|
|
26909
27298
|
*/
|
|
26910
27299
|
getVisibleArea() {
|
|
26911
|
-
|
|
26912
|
-
|
|
26913
|
-
this.getBounds().centerY,
|
|
26914
|
-
this.width,
|
|
26915
|
-
this.height
|
|
26916
|
-
);
|
|
27300
|
+
const b = this.getBounds();
|
|
27301
|
+
return this.visibleArea.setShape(b.centerX, b.centerY, b.width, b.height);
|
|
26917
27302
|
}
|
|
26918
27303
|
/**
|
|
26919
27304
|
* update function
|
|
26920
|
-
* @param {number} dt - time since the last update in milliseconds.
|
|
26921
27305
|
* @returns {boolean} true if dirty
|
|
26922
27306
|
*/
|
|
26923
27307
|
update() {
|
|
@@ -26925,25 +27309,50 @@ var Light2d = class extends Renderable {
|
|
|
26925
27309
|
}
|
|
26926
27310
|
/**
|
|
26927
27311
|
* preDraw this Light2d (automatically called by melonJS)
|
|
26928
|
-
*
|
|
26929
|
-
* to ensure colors blend correctly in the CanvasRenderer.
|
|
26930
|
-
* @param {CanvasRenderer|WebGLRenderer} renderer - a renderer instance
|
|
27312
|
+
* @param {Renderer} renderer - a renderer instance
|
|
26931
27313
|
*/
|
|
26932
27314
|
preDraw(renderer2) {
|
|
26933
27315
|
super.preDraw(renderer2);
|
|
26934
27316
|
renderer2.setBlendMode(this.blendMode);
|
|
26935
27317
|
}
|
|
26936
27318
|
/**
|
|
26937
|
-
* draw this Light2d (automatically called by melonJS)
|
|
26938
|
-
*
|
|
26939
|
-
*
|
|
27319
|
+
* draw this Light2d (automatically called by melonJS).
|
|
27320
|
+
*
|
|
27321
|
+
* Delegates to `renderer.drawLight(this)` — each renderer picks its
|
|
27322
|
+
* own implementation (procedural shader on WebGL; cached `Gradient`
|
|
27323
|
+
* rasterized into a shared `CanvasRenderTarget` on Canvas). Light2d
|
|
27324
|
+
* itself doesn't know which path is used.
|
|
27325
|
+
* @param {Renderer} renderer - a renderer instance
|
|
26940
27326
|
*/
|
|
26941
27327
|
draw(renderer2) {
|
|
26942
|
-
|
|
26943
|
-
|
|
26944
|
-
|
|
26945
|
-
|
|
26946
|
-
|
|
27328
|
+
if (this.illuminationOnly) {
|
|
27329
|
+
return;
|
|
27330
|
+
}
|
|
27331
|
+
renderer2.drawLight(this);
|
|
27332
|
+
}
|
|
27333
|
+
/**
|
|
27334
|
+
* Auto-register this light with the active Stage's lighting set when
|
|
27335
|
+
* added to a container. The Stage uses that set to build the ambient
|
|
27336
|
+
* overlay cutouts; rendering the light itself is handled normally as
|
|
27337
|
+
* part of the world tree walk.
|
|
27338
|
+
* @ignore
|
|
27339
|
+
*/
|
|
27340
|
+
onActivateEvent() {
|
|
27341
|
+
const stage = state_default.current();
|
|
27342
|
+
if (stage && typeof stage._registerLight === "function") {
|
|
27343
|
+
stage._registerLight(this);
|
|
27344
|
+
}
|
|
27345
|
+
}
|
|
27346
|
+
/**
|
|
27347
|
+
* Auto-deregister this light from the active Stage's lighting set when
|
|
27348
|
+
* removed from a container.
|
|
27349
|
+
* @ignore
|
|
27350
|
+
*/
|
|
27351
|
+
onDeactivateEvent() {
|
|
27352
|
+
const stage = state_default.current();
|
|
27353
|
+
if (stage && typeof stage._unregisterLight === "function") {
|
|
27354
|
+
stage._unregisterLight(this);
|
|
27355
|
+
}
|
|
26947
27356
|
}
|
|
26948
27357
|
/**
|
|
26949
27358
|
* Destroy function<br>
|
|
@@ -26952,9 +27361,6 @@ var Light2d = class extends Renderable {
|
|
|
26952
27361
|
destroy() {
|
|
26953
27362
|
colorPool.release(this.color);
|
|
26954
27363
|
this.color = void 0;
|
|
26955
|
-
const renderer2 = this.parentApp?.renderer ?? game.renderer;
|
|
26956
|
-
this.texture.destroy(renderer2);
|
|
26957
|
-
this.texture = void 0;
|
|
26958
27364
|
ellipsePool.release(this.visibleArea);
|
|
26959
27365
|
this.visibleArea = void 0;
|
|
26960
27366
|
super.destroy();
|
|
@@ -27406,7 +27812,7 @@ var Trigger = class extends Renderable {
|
|
|
27406
27812
|
};
|
|
27407
27813
|
|
|
27408
27814
|
// src/version.ts
|
|
27409
|
-
var version = "19.
|
|
27815
|
+
var version = "19.3.0";
|
|
27410
27816
|
|
|
27411
27817
|
// src/system/bootstrap.ts
|
|
27412
27818
|
var initialized = false;
|
|
@@ -30008,114 +30414,92 @@ function generateJoinCircles(centers, radius) {
|
|
|
30008
30414
|
return verts;
|
|
30009
30415
|
}
|
|
30010
30416
|
|
|
30011
|
-
// src/video/webgl/
|
|
30012
|
-
var
|
|
30013
|
-
|
|
30014
|
-
// src/video/webgl/shaders/mesh.vert
|
|
30015
|
-
var mesh_default2 = "// Current vertex point (3D position for mesh rendering)\nattribute vec3 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\n\n// Projection matrix\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\n\nvoid main(void) {\n // Transform the vertex position by the projection matrix\n gl_Position = uProjectionMatrix * vec4(aVertex, 1.0);\n // Pass the remaining attributes to the fragment shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n}\n";
|
|
30417
|
+
// src/video/webgl/lighting/constants.ts
|
|
30418
|
+
var MAX_LIGHTS = 8;
|
|
30016
30419
|
|
|
30017
|
-
// src/video/
|
|
30018
|
-
|
|
30019
|
-
|
|
30020
|
-
|
|
30021
|
-
|
|
30022
|
-
|
|
30023
|
-
this.buffer = new ArrayBuffer(
|
|
30024
|
-
this.maxVertex * this.vertexSize * Float32Array.BYTES_PER_ELEMENT
|
|
30025
|
-
);
|
|
30026
|
-
this.bufferF32 = new Float32Array(this.buffer);
|
|
30027
|
-
this.bufferU32 = new Uint32Array(this.buffer);
|
|
30028
|
-
}
|
|
30029
|
-
/**
|
|
30030
|
-
* clear the vertex array buffer
|
|
30031
|
-
* @ignore
|
|
30032
|
-
*/
|
|
30033
|
-
clear() {
|
|
30034
|
-
this.vertexCount = 0;
|
|
30035
|
-
}
|
|
30036
|
-
/**
|
|
30037
|
-
* return true if full
|
|
30038
|
-
* @ignore
|
|
30039
|
-
*/
|
|
30040
|
-
isFull(vertex) {
|
|
30041
|
-
return this.vertexCount + vertex >= this.maxVertex;
|
|
30042
|
-
}
|
|
30043
|
-
/**
|
|
30044
|
-
* push a new vertex to the buffer
|
|
30045
|
-
* @param {number} x - x position
|
|
30046
|
-
* @param {number} y - y position
|
|
30047
|
-
* @param {number} u - texture U coordinate
|
|
30048
|
-
* @param {number} v - texture V coordinate
|
|
30049
|
-
* @param {number} tint - tint color in UINT32 (argb) format
|
|
30050
|
-
* @param {number} [textureId] - texture unit index for multi-texture batching
|
|
30051
|
-
* @ignore
|
|
30052
|
-
*/
|
|
30053
|
-
push(x, y, u, v, tint, textureId) {
|
|
30054
|
-
const offset = this.vertexCount * this.vertexSize;
|
|
30055
|
-
this.bufferF32[offset] = x;
|
|
30056
|
-
this.bufferF32[offset + 1] = y;
|
|
30057
|
-
this.bufferF32[offset + 2] = u;
|
|
30058
|
-
this.bufferF32[offset + 3] = v;
|
|
30059
|
-
this.bufferU32[offset + 4] = tint;
|
|
30060
|
-
if (this.vertexSize > 5) {
|
|
30061
|
-
this.bufferF32[offset + 5] = textureId || 0;
|
|
30062
|
-
}
|
|
30063
|
-
this.vertexCount++;
|
|
30064
|
-
return this;
|
|
30065
|
-
}
|
|
30066
|
-
/**
|
|
30067
|
-
* push a new vertex with all-float data to the buffer
|
|
30068
|
-
* @param {ArrayLike<number>} data - float values for one vertex
|
|
30069
|
-
* @param {number} srcOffset - start index in the source data
|
|
30070
|
-
* @param {number} count - number of floats to copy (should equal vertexSize)
|
|
30071
|
-
* @ignore
|
|
30072
|
-
*/
|
|
30073
|
-
pushFloats(data2, srcOffset, count) {
|
|
30074
|
-
const offset = this.vertexCount * this.vertexSize;
|
|
30075
|
-
for (let i = 0; i < count; i++) {
|
|
30076
|
-
this.bufferF32[offset + i] = data2[srcOffset + i];
|
|
30077
|
-
}
|
|
30078
|
-
this.vertexCount++;
|
|
30079
|
-
return this;
|
|
30080
|
-
}
|
|
30081
|
-
/**
|
|
30082
|
-
* push a new vertex to the buffer (mesh format: x, y, z, u, v, tint)
|
|
30083
|
-
* @ignore
|
|
30084
|
-
*/
|
|
30085
|
-
pushMesh(x, y, z, u, v, tint) {
|
|
30086
|
-
const offset = this.vertexCount * this.vertexSize;
|
|
30087
|
-
this.bufferF32[offset] = x;
|
|
30088
|
-
this.bufferF32[offset + 1] = y;
|
|
30089
|
-
this.bufferF32[offset + 2] = z;
|
|
30090
|
-
this.bufferF32[offset + 3] = u;
|
|
30091
|
-
this.bufferF32[offset + 4] = v;
|
|
30092
|
-
this.bufferU32[offset + 5] = tint;
|
|
30093
|
-
this.vertexCount++;
|
|
30094
|
-
return this;
|
|
30095
|
-
}
|
|
30096
|
-
/**
|
|
30097
|
-
* return a reference to the data in Float32 format
|
|
30098
|
-
* @ignore
|
|
30099
|
-
*/
|
|
30100
|
-
toFloat32(begin, end) {
|
|
30101
|
-
if (typeof end !== "undefined") {
|
|
30102
|
-
return this.bufferF32.subarray(begin, end);
|
|
30420
|
+
// src/video/webgl/shaders/multitexture-lit.js
|
|
30421
|
+
function buildSamplerSelect(varName, samplerPrefix, count, target) {
|
|
30422
|
+
const lines = [];
|
|
30423
|
+
for (let i = 0; i < count; i++) {
|
|
30424
|
+
if (i === 0) {
|
|
30425
|
+
lines.push(" if (" + varName + " < 0.5) {");
|
|
30103
30426
|
} else {
|
|
30104
|
-
|
|
30427
|
+
lines.push(" } else if (" + varName + " < " + (i + 0.5) + ") {");
|
|
30105
30428
|
}
|
|
30429
|
+
lines.push(
|
|
30430
|
+
" " + target + " = texture2D(" + samplerPrefix + i + ", vRegion);"
|
|
30431
|
+
);
|
|
30106
30432
|
}
|
|
30107
|
-
|
|
30108
|
-
|
|
30109
|
-
|
|
30110
|
-
|
|
30111
|
-
|
|
30112
|
-
|
|
30113
|
-
|
|
30114
|
-
|
|
30115
|
-
|
|
30116
|
-
|
|
30433
|
+
lines.push(" } else {");
|
|
30434
|
+
lines.push(
|
|
30435
|
+
" " + target + " = texture2D(" + samplerPrefix + "0, vRegion);"
|
|
30436
|
+
);
|
|
30437
|
+
lines.push(" }");
|
|
30438
|
+
return lines;
|
|
30439
|
+
}
|
|
30440
|
+
function buildLitMultiTextureFragment(maxTextures) {
|
|
30441
|
+
const count = Math.max(maxTextures, 1);
|
|
30442
|
+
const lines = [];
|
|
30443
|
+
for (let i = 0; i < count; i++) {
|
|
30444
|
+
lines.push("uniform sampler2D uSampler" + i + ";");
|
|
30117
30445
|
}
|
|
30118
|
-
|
|
30446
|
+
for (let i = 0; i < count; i++) {
|
|
30447
|
+
lines.push("uniform sampler2D uNormalSampler" + i + ";");
|
|
30448
|
+
}
|
|
30449
|
+
lines.push("uniform int uLightCount;");
|
|
30450
|
+
lines.push("uniform vec4 uLightPos[" + MAX_LIGHTS + "];");
|
|
30451
|
+
lines.push("uniform vec3 uLightColor[" + MAX_LIGHTS + "];");
|
|
30452
|
+
lines.push("uniform float uLightHeight[" + MAX_LIGHTS + "];");
|
|
30453
|
+
lines.push("uniform vec3 uAmbient;");
|
|
30454
|
+
lines.push("varying vec4 vColor;");
|
|
30455
|
+
lines.push("varying vec2 vRegion;");
|
|
30456
|
+
lines.push("varying float vTextureId;");
|
|
30457
|
+
lines.push("varying float vNormalTextureId;");
|
|
30458
|
+
lines.push("varying vec2 vWorldPos;");
|
|
30459
|
+
lines.push("");
|
|
30460
|
+
lines.push("void main(void) {");
|
|
30461
|
+
lines.push(" vec4 color;");
|
|
30462
|
+
lines.push(...buildSamplerSelect("vTextureId", "uSampler", count, "color"));
|
|
30463
|
+
lines.push(" if (vNormalTextureId < -0.5) {");
|
|
30464
|
+
lines.push(" gl_FragColor = color * vColor;");
|
|
30465
|
+
lines.push(" return;");
|
|
30466
|
+
lines.push(" }");
|
|
30467
|
+
lines.push(" vec4 normalSample;");
|
|
30468
|
+
lines.push(
|
|
30469
|
+
...buildSamplerSelect(
|
|
30470
|
+
"vNormalTextureId",
|
|
30471
|
+
"uNormalSampler",
|
|
30472
|
+
count,
|
|
30473
|
+
"normalSample"
|
|
30474
|
+
)
|
|
30475
|
+
);
|
|
30476
|
+
lines.push(
|
|
30477
|
+
" vec3 normal = normalize(normalSample.rgb * 2.0 - vec3(1.0));"
|
|
30478
|
+
);
|
|
30479
|
+
lines.push(" normal.y = -normal.y;");
|
|
30480
|
+
lines.push(" vec3 lighting = uAmbient;");
|
|
30481
|
+
lines.push(" for (int i = 0; i < " + MAX_LIGHTS + "; i++) {");
|
|
30482
|
+
lines.push(" if (i >= uLightCount) break;");
|
|
30483
|
+
lines.push(" vec4 lp = uLightPos[i];");
|
|
30484
|
+
lines.push(" vec2 toLight = lp.xy - vWorldPos;");
|
|
30485
|
+
lines.push(" float dist = length(toLight);");
|
|
30486
|
+
lines.push(" float linear = max(0.0, 1.0 - dist / max(lp.z, 1.0));");
|
|
30487
|
+
lines.push(" float att = linear * linear;");
|
|
30488
|
+
lines.push(
|
|
30489
|
+
" vec3 lightDir = normalize(vec3(toLight, uLightHeight[i]));"
|
|
30490
|
+
);
|
|
30491
|
+
lines.push(" float NdotL = max(0.0, dot(normal, lightDir));");
|
|
30492
|
+
lines.push(" lighting += uLightColor[i] * (lp.w * att * NdotL);");
|
|
30493
|
+
lines.push(" }");
|
|
30494
|
+
lines.push(
|
|
30495
|
+
" gl_FragColor = vec4(color.rgb * lighting, color.a) * vColor;"
|
|
30496
|
+
);
|
|
30497
|
+
lines.push("}");
|
|
30498
|
+
return lines.join("\n");
|
|
30499
|
+
}
|
|
30500
|
+
|
|
30501
|
+
// src/video/webgl/shaders/quad-multi-lit.vert
|
|
30502
|
+
var quad_multi_lit_default = "// Lit-aware vertex shader used by `LitQuadBatcher` (the SpriteIlluminator\n// path). Carries a paired `aNormalTextureId` per vertex so the fragment\n// shader knows which `uNormalSampler<n>` to read, and `vWorldPos` so the\n// lit math can compute per-fragment `lightPos - pos` deltas.\nattribute vec2 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\nattribute float aTextureId;\nattribute float aNormalTextureId;\n\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\nvarying float vTextureId;\nvarying float vNormalTextureId;\n// Pre-projection vertex position (in the renderer's pre-projection\n// space \u2014 typically camera-local for default cameras with the world\n// container's translate applied). Used by the lit fragment path to\n// compute `lightPos - fragmentPos` for each Light2d.\nvarying vec2 vWorldPos;\n\nvoid main(void) {\n gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0);\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n vTextureId = aTextureId;\n vNormalTextureId = aNormalTextureId;\n vWorldPos = aVertex;\n}\n";
|
|
30119
30503
|
|
|
30120
30504
|
// src/video/buffer/index.js
|
|
30121
30505
|
var IndexBuffer = class {
|
|
@@ -30216,6 +30600,145 @@ var WebGLIndexBuffer = class extends IndexBuffer {
|
|
|
30216
30600
|
}
|
|
30217
30601
|
};
|
|
30218
30602
|
|
|
30603
|
+
// src/video/webgl/shaders/multitexture.js
|
|
30604
|
+
function buildMultiTextureFragment(maxTextures) {
|
|
30605
|
+
const count = Math.max(maxTextures, 1);
|
|
30606
|
+
const lines = [];
|
|
30607
|
+
for (let i = 0; i < count; i++) {
|
|
30608
|
+
lines.push("uniform sampler2D uSampler" + i + ";");
|
|
30609
|
+
}
|
|
30610
|
+
lines.push("varying vec4 vColor;");
|
|
30611
|
+
lines.push("varying vec2 vRegion;");
|
|
30612
|
+
lines.push("varying float vTextureId;");
|
|
30613
|
+
lines.push("");
|
|
30614
|
+
lines.push("void main(void) {");
|
|
30615
|
+
lines.push(" vec4 color;");
|
|
30616
|
+
for (let i = 0; i < count; i++) {
|
|
30617
|
+
if (i === 0) {
|
|
30618
|
+
lines.push(" if (vTextureId < 0.5) {");
|
|
30619
|
+
} else {
|
|
30620
|
+
lines.push(" } else if (vTextureId < " + (i + 0.5) + ") {");
|
|
30621
|
+
}
|
|
30622
|
+
lines.push(" color = texture2D(uSampler" + i + ", vRegion);");
|
|
30623
|
+
}
|
|
30624
|
+
lines.push(" } else {");
|
|
30625
|
+
lines.push(" color = texture2D(uSampler0, vRegion);");
|
|
30626
|
+
lines.push(" }");
|
|
30627
|
+
lines.push(" gl_FragColor = color * vColor;");
|
|
30628
|
+
lines.push("}");
|
|
30629
|
+
return lines.join("\n");
|
|
30630
|
+
}
|
|
30631
|
+
|
|
30632
|
+
// src/video/webgl/shaders/quad-multi.vert
|
|
30633
|
+
var quad_multi_default = "// Current vertex point\nattribute vec2 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\nattribute float aTextureId;\n\n// Projection matrix\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\nvarying float vTextureId;\n\nvoid main(void) {\n // Transform the vertex position by the projection matrix\n gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0);\n // Pass the remaining attributes to the fragment shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n vTextureId = aTextureId;\n}\n";
|
|
30634
|
+
|
|
30635
|
+
// src/video/buffer/vertex.js
|
|
30636
|
+
var VertexArrayBuffer = class {
|
|
30637
|
+
constructor(vertexSize, maxVertex) {
|
|
30638
|
+
this.vertexSize = vertexSize;
|
|
30639
|
+
this.maxVertex = maxVertex;
|
|
30640
|
+
this.vertexCount = 0;
|
|
30641
|
+
this.buffer = new ArrayBuffer(
|
|
30642
|
+
this.maxVertex * this.vertexSize * Float32Array.BYTES_PER_ELEMENT
|
|
30643
|
+
);
|
|
30644
|
+
this.bufferF32 = new Float32Array(this.buffer);
|
|
30645
|
+
this.bufferU32 = new Uint32Array(this.buffer);
|
|
30646
|
+
}
|
|
30647
|
+
/**
|
|
30648
|
+
* clear the vertex array buffer
|
|
30649
|
+
* @ignore
|
|
30650
|
+
*/
|
|
30651
|
+
clear() {
|
|
30652
|
+
this.vertexCount = 0;
|
|
30653
|
+
}
|
|
30654
|
+
/**
|
|
30655
|
+
* return true if full
|
|
30656
|
+
* @ignore
|
|
30657
|
+
*/
|
|
30658
|
+
isFull(vertex) {
|
|
30659
|
+
return this.vertexCount + vertex >= this.maxVertex;
|
|
30660
|
+
}
|
|
30661
|
+
/**
|
|
30662
|
+
* push a new vertex to the buffer
|
|
30663
|
+
* @param {number} x - x position
|
|
30664
|
+
* @param {number} y - y position
|
|
30665
|
+
* @param {number} u - texture U coordinate
|
|
30666
|
+
* @param {number} v - texture V coordinate
|
|
30667
|
+
* @param {number} tint - tint color in UINT32 (argb) format
|
|
30668
|
+
* @param {number} [textureId] - texture unit index for multi-texture batching
|
|
30669
|
+
* @param {number} [normalTextureId] - paired normal-map texture unit index, or `-1` for unlit quads
|
|
30670
|
+
* @ignore
|
|
30671
|
+
*/
|
|
30672
|
+
push(x, y, u, v, tint, textureId, normalTextureId) {
|
|
30673
|
+
const offset = this.vertexCount * this.vertexSize;
|
|
30674
|
+
this.bufferF32[offset] = x;
|
|
30675
|
+
this.bufferF32[offset + 1] = y;
|
|
30676
|
+
this.bufferF32[offset + 2] = u;
|
|
30677
|
+
this.bufferF32[offset + 3] = v;
|
|
30678
|
+
this.bufferU32[offset + 4] = tint;
|
|
30679
|
+
if (this.vertexSize > 5) {
|
|
30680
|
+
this.bufferF32[offset + 5] = textureId || 0;
|
|
30681
|
+
if (this.vertexSize > 6) {
|
|
30682
|
+
this.bufferF32[offset + 6] = typeof normalTextureId === "number" ? normalTextureId : -1;
|
|
30683
|
+
}
|
|
30684
|
+
}
|
|
30685
|
+
this.vertexCount++;
|
|
30686
|
+
return this;
|
|
30687
|
+
}
|
|
30688
|
+
/**
|
|
30689
|
+
* push a new vertex with all-float data to the buffer
|
|
30690
|
+
* @param {ArrayLike<number>} data - float values for one vertex
|
|
30691
|
+
* @param {number} srcOffset - start index in the source data
|
|
30692
|
+
* @param {number} count - number of floats to copy (should equal vertexSize)
|
|
30693
|
+
* @ignore
|
|
30694
|
+
*/
|
|
30695
|
+
pushFloats(data2, srcOffset, count) {
|
|
30696
|
+
const offset = this.vertexCount * this.vertexSize;
|
|
30697
|
+
for (let i = 0; i < count; i++) {
|
|
30698
|
+
this.bufferF32[offset + i] = data2[srcOffset + i];
|
|
30699
|
+
}
|
|
30700
|
+
this.vertexCount++;
|
|
30701
|
+
return this;
|
|
30702
|
+
}
|
|
30703
|
+
/**
|
|
30704
|
+
* push a new vertex to the buffer (mesh format: x, y, z, u, v, tint)
|
|
30705
|
+
* @ignore
|
|
30706
|
+
*/
|
|
30707
|
+
pushMesh(x, y, z, u, v, tint) {
|
|
30708
|
+
const offset = this.vertexCount * this.vertexSize;
|
|
30709
|
+
this.bufferF32[offset] = x;
|
|
30710
|
+
this.bufferF32[offset + 1] = y;
|
|
30711
|
+
this.bufferF32[offset + 2] = z;
|
|
30712
|
+
this.bufferF32[offset + 3] = u;
|
|
30713
|
+
this.bufferF32[offset + 4] = v;
|
|
30714
|
+
this.bufferU32[offset + 5] = tint;
|
|
30715
|
+
this.vertexCount++;
|
|
30716
|
+
return this;
|
|
30717
|
+
}
|
|
30718
|
+
/**
|
|
30719
|
+
* return a reference to the data in Float32 format
|
|
30720
|
+
* @ignore
|
|
30721
|
+
*/
|
|
30722
|
+
toFloat32(begin, end) {
|
|
30723
|
+
if (typeof end !== "undefined") {
|
|
30724
|
+
return this.bufferF32.subarray(begin, end);
|
|
30725
|
+
} else {
|
|
30726
|
+
return this.bufferF32;
|
|
30727
|
+
}
|
|
30728
|
+
}
|
|
30729
|
+
/**
|
|
30730
|
+
* return a reference to the data in Uint32 format
|
|
30731
|
+
* @ignore
|
|
30732
|
+
*/
|
|
30733
|
+
toUint32(begin, end) {
|
|
30734
|
+
if (typeof end !== "undefined") {
|
|
30735
|
+
return this.bufferU32.subarray(begin, end);
|
|
30736
|
+
} else {
|
|
30737
|
+
return this.bufferU32;
|
|
30738
|
+
}
|
|
30739
|
+
}
|
|
30740
|
+
};
|
|
30741
|
+
|
|
30219
30742
|
// src/video/webgl/batchers/batcher.js
|
|
30220
30743
|
var DEFAULT_MAX_VERTICES = 4096;
|
|
30221
30744
|
var Batcher = class {
|
|
@@ -30314,6 +30837,25 @@ var Batcher = class {
|
|
|
30314
30837
|
this.useShader(this.defaultShader);
|
|
30315
30838
|
}
|
|
30316
30839
|
}
|
|
30840
|
+
/**
|
|
30841
|
+
* called by the WebGL renderer when this batcher is being replaced by another.
|
|
30842
|
+
* Disables this batcher's vertex attribute locations so they don't leak across
|
|
30843
|
+
* (otherwise stale stride/offset state can cause INVALID_OPERATION on the next draw).
|
|
30844
|
+
*/
|
|
30845
|
+
unbind() {
|
|
30846
|
+
if (this.currentShader === void 0) {
|
|
30847
|
+
return;
|
|
30848
|
+
}
|
|
30849
|
+
const gl = this.gl;
|
|
30850
|
+
for (let i = 0; i < this.attributes.length; ++i) {
|
|
30851
|
+
const location = this.currentShader.getAttribLocation(
|
|
30852
|
+
this.attributes[i].name
|
|
30853
|
+
);
|
|
30854
|
+
if (location !== -1) {
|
|
30855
|
+
gl.disableVertexAttribArray(location);
|
|
30856
|
+
}
|
|
30857
|
+
}
|
|
30858
|
+
}
|
|
30317
30859
|
/**
|
|
30318
30860
|
* Select the shader to use for compositing
|
|
30319
30861
|
* @see GLShader
|
|
@@ -30322,6 +30864,9 @@ var Batcher = class {
|
|
|
30322
30864
|
useShader(shader) {
|
|
30323
30865
|
if (this.currentShader !== shader || this.renderer.currentProgram !== shader.program) {
|
|
30324
30866
|
this.flush();
|
|
30867
|
+
if (this.currentShader && this.currentShader !== shader) {
|
|
30868
|
+
this.unbind();
|
|
30869
|
+
}
|
|
30325
30870
|
shader.bind();
|
|
30326
30871
|
shader.setUniform(this.projectionUniform, this.renderer.projectionMatrix);
|
|
30327
30872
|
shader.setVertexAttributes(this.gl, this.attributes, this.stride);
|
|
@@ -30667,6 +31212,514 @@ var MaterialBatcher = class extends Batcher {
|
|
|
30667
31212
|
}
|
|
30668
31213
|
};
|
|
30669
31214
|
|
|
31215
|
+
// src/video/webgl/batchers/quad_batcher.js
|
|
31216
|
+
var V_ARRAY = [
|
|
31217
|
+
new Vector2d(),
|
|
31218
|
+
new Vector2d(),
|
|
31219
|
+
new Vector2d(),
|
|
31220
|
+
new Vector2d()
|
|
31221
|
+
];
|
|
31222
|
+
var QuadBatcher = class extends MaterialBatcher {
|
|
31223
|
+
/**
|
|
31224
|
+
* Initialize the compositor
|
|
31225
|
+
* @ignore
|
|
31226
|
+
*/
|
|
31227
|
+
init(renderer2) {
|
|
31228
|
+
this.maxBatchTextures = Math.min(renderer2.maxTextures, 16);
|
|
31229
|
+
super.init(renderer2, {
|
|
31230
|
+
attributes: [
|
|
31231
|
+
{
|
|
31232
|
+
name: "aVertex",
|
|
31233
|
+
size: 2,
|
|
31234
|
+
type: renderer2.gl.FLOAT,
|
|
31235
|
+
normalized: false,
|
|
31236
|
+
offset: 0 * Float32Array.BYTES_PER_ELEMENT
|
|
31237
|
+
},
|
|
31238
|
+
{
|
|
31239
|
+
name: "aRegion",
|
|
31240
|
+
size: 2,
|
|
31241
|
+
type: renderer2.gl.FLOAT,
|
|
31242
|
+
normalized: false,
|
|
31243
|
+
offset: 2 * Float32Array.BYTES_PER_ELEMENT
|
|
31244
|
+
},
|
|
31245
|
+
{
|
|
31246
|
+
name: "aColor",
|
|
31247
|
+
size: 4,
|
|
31248
|
+
type: renderer2.gl.UNSIGNED_BYTE,
|
|
31249
|
+
normalized: true,
|
|
31250
|
+
offset: 4 * Float32Array.BYTES_PER_ELEMENT
|
|
31251
|
+
},
|
|
31252
|
+
{
|
|
31253
|
+
name: "aTextureId",
|
|
31254
|
+
size: 1,
|
|
31255
|
+
type: renderer2.gl.FLOAT,
|
|
31256
|
+
normalized: false,
|
|
31257
|
+
offset: 5 * Float32Array.BYTES_PER_ELEMENT
|
|
31258
|
+
}
|
|
31259
|
+
],
|
|
31260
|
+
shader: {
|
|
31261
|
+
vertex: quad_multi_default,
|
|
31262
|
+
fragment: buildMultiTextureFragment(this.maxBatchTextures)
|
|
31263
|
+
}
|
|
31264
|
+
});
|
|
31265
|
+
this.bindColorSamplers();
|
|
31266
|
+
this.useMultiTexture = true;
|
|
31267
|
+
this.createIndexBuffer();
|
|
31268
|
+
}
|
|
31269
|
+
/**
|
|
31270
|
+
* (Re-)create the index buffer for quad batching (4 verts + 6 indices per quad).
|
|
31271
|
+
* Called from `init` and `reset` (after context loss).
|
|
31272
|
+
* @ignore
|
|
31273
|
+
*/
|
|
31274
|
+
createIndexBuffer() {
|
|
31275
|
+
const maxQuads = this.vertexData.maxVertex / 4;
|
|
31276
|
+
this.indexBuffer = new WebGLIndexBuffer(
|
|
31277
|
+
this.gl,
|
|
31278
|
+
maxQuads * 6,
|
|
31279
|
+
this.renderer.WebGLVersion > 1
|
|
31280
|
+
);
|
|
31281
|
+
this.indexBuffer.fillQuadPattern(maxQuads);
|
|
31282
|
+
}
|
|
31283
|
+
/**
|
|
31284
|
+
* Bind the color sampler uniforms (`uSampler0..uSamplerN-1`) to their
|
|
31285
|
+
* respective texture units. Called from `init` and `reset`.
|
|
31286
|
+
* @ignore
|
|
31287
|
+
*/
|
|
31288
|
+
bindColorSamplers() {
|
|
31289
|
+
for (let i = 0; i < this.maxBatchTextures; i++) {
|
|
31290
|
+
this.defaultShader.setUniform("uSampler" + i, i);
|
|
31291
|
+
}
|
|
31292
|
+
}
|
|
31293
|
+
/**
|
|
31294
|
+
* Select the shader to use for compositing.
|
|
31295
|
+
* Multi-texture batching is automatically enabled when the default
|
|
31296
|
+
* shader is active, and disabled for custom ShaderEffect shaders.
|
|
31297
|
+
* @see GLShader
|
|
31298
|
+
* @see ShaderEffect
|
|
31299
|
+
* @param {GLShader|ShaderEffect} shader - a reference to a GLShader or ShaderEffect instance
|
|
31300
|
+
*/
|
|
31301
|
+
useShader(shader) {
|
|
31302
|
+
super.useShader(shader);
|
|
31303
|
+
this.useMultiTexture = shader === this.defaultShader;
|
|
31304
|
+
}
|
|
31305
|
+
/**
|
|
31306
|
+
* Reset compositor internal state
|
|
31307
|
+
* @ignore
|
|
31308
|
+
*/
|
|
31309
|
+
reset() {
|
|
31310
|
+
super.reset();
|
|
31311
|
+
this.createIndexBuffer();
|
|
31312
|
+
this.bindColorSamplers();
|
|
31313
|
+
this.useMultiTexture = true;
|
|
31314
|
+
}
|
|
31315
|
+
/**
|
|
31316
|
+
* Flush batched texture data to the GPU using indexed drawing.
|
|
31317
|
+
* @param {number} [mode=gl.TRIANGLES] - the GL drawing mode
|
|
31318
|
+
*/
|
|
31319
|
+
flush(mode = this.mode) {
|
|
31320
|
+
const vertex = this.vertexData;
|
|
31321
|
+
const vertexCount = vertex.vertexCount;
|
|
31322
|
+
if (vertexCount > 0) {
|
|
31323
|
+
const gl = this.gl;
|
|
31324
|
+
const vertexSize = vertex.vertexSize;
|
|
31325
|
+
this.indexBuffer.bind();
|
|
31326
|
+
if (this.renderer.WebGLVersion > 1) {
|
|
31327
|
+
gl.bufferData(
|
|
31328
|
+
gl.ARRAY_BUFFER,
|
|
31329
|
+
vertex.toFloat32(),
|
|
31330
|
+
gl.STREAM_DRAW,
|
|
31331
|
+
0,
|
|
31332
|
+
vertexCount * vertexSize
|
|
31333
|
+
);
|
|
31334
|
+
} else {
|
|
31335
|
+
gl.bufferData(
|
|
31336
|
+
gl.ARRAY_BUFFER,
|
|
31337
|
+
vertex.toFloat32(0, vertexCount * vertexSize),
|
|
31338
|
+
gl.STREAM_DRAW
|
|
31339
|
+
);
|
|
31340
|
+
}
|
|
31341
|
+
const indexCount = vertexCount / 4 * 6;
|
|
31342
|
+
gl.drawElements(mode, indexCount, this.indexBuffer.type, 0);
|
|
31343
|
+
vertex.clear();
|
|
31344
|
+
}
|
|
31345
|
+
}
|
|
31346
|
+
/**
|
|
31347
|
+
* Draw a screen-aligned quad with the given raw WebGL texture through the given shader.
|
|
31348
|
+
* Binds the texture to unit 0, pushes 4 vertices (Y-flipped UVs), flushes,
|
|
31349
|
+
* then unbinds the texture.
|
|
31350
|
+
* @param {WebGLTexture} source - the raw GL texture to blit
|
|
31351
|
+
* @param {number} x - destination x
|
|
31352
|
+
* @param {number} y - destination y
|
|
31353
|
+
* @param {number} width - destination width
|
|
31354
|
+
* @param {number} height - destination height
|
|
31355
|
+
* @param {GLShader|ShaderEffect} shader - the shader effect to apply
|
|
31356
|
+
*/
|
|
31357
|
+
blitTexture(source, x, y, width, height, shader) {
|
|
31358
|
+
const gl = this.gl;
|
|
31359
|
+
this.useShader(shader);
|
|
31360
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
31361
|
+
gl.bindTexture(gl.TEXTURE_2D, source);
|
|
31362
|
+
this.currentTextureUnit = 0;
|
|
31363
|
+
this.boundTextures[0] = source;
|
|
31364
|
+
shader.setUniform("uSampler", 0);
|
|
31365
|
+
const m = this.viewMatrix;
|
|
31366
|
+
const vec0 = V_ARRAY[0].set(x, y);
|
|
31367
|
+
const vec1 = V_ARRAY[1].set(x + width, y);
|
|
31368
|
+
const vec2 = V_ARRAY[2].set(x, y + height);
|
|
31369
|
+
const vec3 = V_ARRAY[3].set(x + width, y + height);
|
|
31370
|
+
if (m && !m.isIdentity()) {
|
|
31371
|
+
m.apply(vec0);
|
|
31372
|
+
m.apply(vec1);
|
|
31373
|
+
m.apply(vec2);
|
|
31374
|
+
m.apply(vec3);
|
|
31375
|
+
}
|
|
31376
|
+
const tint = 4294967295;
|
|
31377
|
+
this.vertexData.push(vec0.x, vec0.y, 0, 1, tint, 0);
|
|
31378
|
+
this.vertexData.push(vec1.x, vec1.y, 1, 1, tint, 0);
|
|
31379
|
+
this.vertexData.push(vec2.x, vec2.y, 0, 0, tint, 0);
|
|
31380
|
+
this.vertexData.push(vec3.x, vec3.y, 1, 0, tint, 0);
|
|
31381
|
+
this.flush();
|
|
31382
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
31383
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
31384
|
+
this.currentTextureUnit = -1;
|
|
31385
|
+
delete this.boundTextures[0];
|
|
31386
|
+
this.useShader(this.defaultShader);
|
|
31387
|
+
}
|
|
31388
|
+
/**
|
|
31389
|
+
* Add a textured quad
|
|
31390
|
+
* @param {TextureAtlas} texture - Source texture atlas
|
|
31391
|
+
* @param {number} x - Destination x-coordinate
|
|
31392
|
+
* @param {number} y - Destination y-coordinate
|
|
31393
|
+
* @param {number} w - Destination width
|
|
31394
|
+
* @param {number} h - Destination height
|
|
31395
|
+
* @param {number} u0 - Texture UV (u0) value.
|
|
31396
|
+
* @param {number} v0 - Texture UV (v0) value.
|
|
31397
|
+
* @param {number} u1 - Texture UV (u1) value.
|
|
31398
|
+
* @param {number} v1 - Texture UV (v1) value.
|
|
31399
|
+
* @param {number} tint - tint color to be applied to the texture in UINT32 (argb) format
|
|
31400
|
+
* @param {boolean} [reupload=false] - Force the texture to be reuploaded even if already bound
|
|
31401
|
+
*/
|
|
31402
|
+
addQuad(texture, x, y, w, h, u0, v0, u1, v1, tint, reupload = false) {
|
|
31403
|
+
const vertexData = this.vertexData;
|
|
31404
|
+
if (vertexData.isFull(4)) {
|
|
31405
|
+
this.flush();
|
|
31406
|
+
}
|
|
31407
|
+
let unit;
|
|
31408
|
+
if (this.useMultiTexture) {
|
|
31409
|
+
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
31410
|
+
if (unit >= this.maxBatchTextures) {
|
|
31411
|
+
this.flush();
|
|
31412
|
+
this.renderer.cache.resetUnitAssignments();
|
|
31413
|
+
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
31414
|
+
}
|
|
31415
|
+
} else {
|
|
31416
|
+
unit = this.uploadTexture(texture, w, h, reupload);
|
|
31417
|
+
if (unit !== this.currentSamplerUnit) {
|
|
31418
|
+
this.currentShader.setUniform("uSampler", unit);
|
|
31419
|
+
this.currentSamplerUnit = unit;
|
|
31420
|
+
}
|
|
31421
|
+
}
|
|
31422
|
+
const m = this.viewMatrix;
|
|
31423
|
+
const vec0 = V_ARRAY[0].set(x, y);
|
|
31424
|
+
const vec1 = V_ARRAY[1].set(x + w, y);
|
|
31425
|
+
const vec2 = V_ARRAY[2].set(x, y + h);
|
|
31426
|
+
const vec3 = V_ARRAY[3].set(x + w, y + h);
|
|
31427
|
+
if (!m.isIdentity()) {
|
|
31428
|
+
m.apply(vec0);
|
|
31429
|
+
m.apply(vec1);
|
|
31430
|
+
m.apply(vec2);
|
|
31431
|
+
m.apply(vec3);
|
|
31432
|
+
}
|
|
31433
|
+
const textureId = this.useMultiTexture ? unit : 0;
|
|
31434
|
+
vertexData.push(vec0.x, vec0.y, u0, v0, tint, textureId);
|
|
31435
|
+
vertexData.push(vec1.x, vec1.y, u1, v0, tint, textureId);
|
|
31436
|
+
vertexData.push(vec2.x, vec2.y, u0, v1, tint, textureId);
|
|
31437
|
+
vertexData.push(vec3.x, vec3.y, u1, v1, tint, textureId);
|
|
31438
|
+
}
|
|
31439
|
+
};
|
|
31440
|
+
|
|
31441
|
+
// src/video/webgl/batchers/lit_quad_batcher.js
|
|
31442
|
+
var LitQuadBatcher = class extends QuadBatcher {
|
|
31443
|
+
/**
|
|
31444
|
+
* @ignore
|
|
31445
|
+
*/
|
|
31446
|
+
init(renderer2) {
|
|
31447
|
+
const halved = Math.min(
|
|
31448
|
+
Math.max(1, Math.floor(renderer2.maxTextures / 2)),
|
|
31449
|
+
16
|
|
31450
|
+
);
|
|
31451
|
+
this.maxBatchTextures = halved;
|
|
31452
|
+
Object.getPrototypeOf(QuadBatcher.prototype).init.call(this, renderer2, {
|
|
31453
|
+
attributes: [
|
|
31454
|
+
{
|
|
31455
|
+
name: "aVertex",
|
|
31456
|
+
size: 2,
|
|
31457
|
+
type: renderer2.gl.FLOAT,
|
|
31458
|
+
normalized: false,
|
|
31459
|
+
offset: 0 * Float32Array.BYTES_PER_ELEMENT
|
|
31460
|
+
},
|
|
31461
|
+
{
|
|
31462
|
+
name: "aRegion",
|
|
31463
|
+
size: 2,
|
|
31464
|
+
type: renderer2.gl.FLOAT,
|
|
31465
|
+
normalized: false,
|
|
31466
|
+
offset: 2 * Float32Array.BYTES_PER_ELEMENT
|
|
31467
|
+
},
|
|
31468
|
+
{
|
|
31469
|
+
name: "aColor",
|
|
31470
|
+
size: 4,
|
|
31471
|
+
type: renderer2.gl.UNSIGNED_BYTE,
|
|
31472
|
+
normalized: true,
|
|
31473
|
+
offset: 4 * Float32Array.BYTES_PER_ELEMENT
|
|
31474
|
+
},
|
|
31475
|
+
{
|
|
31476
|
+
name: "aTextureId",
|
|
31477
|
+
size: 1,
|
|
31478
|
+
type: renderer2.gl.FLOAT,
|
|
31479
|
+
normalized: false,
|
|
31480
|
+
offset: 5 * Float32Array.BYTES_PER_ELEMENT
|
|
31481
|
+
},
|
|
31482
|
+
{
|
|
31483
|
+
name: "aNormalTextureId",
|
|
31484
|
+
size: 1,
|
|
31485
|
+
type: renderer2.gl.FLOAT,
|
|
31486
|
+
normalized: false,
|
|
31487
|
+
offset: 6 * Float32Array.BYTES_PER_ELEMENT
|
|
31488
|
+
}
|
|
31489
|
+
],
|
|
31490
|
+
shader: {
|
|
31491
|
+
vertex: quad_multi_lit_default,
|
|
31492
|
+
fragment: buildLitMultiTextureFragment(halved)
|
|
31493
|
+
}
|
|
31494
|
+
});
|
|
31495
|
+
this.bindColorSamplers();
|
|
31496
|
+
this.bindNormalSamplers();
|
|
31497
|
+
this.createIndexBuffer();
|
|
31498
|
+
this.useMultiTexture = true;
|
|
31499
|
+
this.boundNormalMaps = new Array(halved).fill(null);
|
|
31500
|
+
this.normalMapTextures = /* @__PURE__ */ new Map();
|
|
31501
|
+
this._lightCount = 0;
|
|
31502
|
+
this._maxLights = MAX_LIGHTS;
|
|
31503
|
+
this.defaultShader.setUniform("uLightCount", 0);
|
|
31504
|
+
this.defaultShader.setUniform("uAmbient", [0, 0, 0]);
|
|
31505
|
+
}
|
|
31506
|
+
/**
|
|
31507
|
+
* Bind the paired normal sampler uniforms (`uNormalSampler0..N-1`)
|
|
31508
|
+
* to texture units `maxBatchTextures..2*maxBatchTextures-1`. Called
|
|
31509
|
+
* from `init` and `reset`.
|
|
31510
|
+
* @ignore
|
|
31511
|
+
*/
|
|
31512
|
+
bindNormalSamplers() {
|
|
31513
|
+
for (let i = 0; i < this.maxBatchTextures; i++) {
|
|
31514
|
+
this.defaultShader.setUniform(
|
|
31515
|
+
"uNormalSampler" + i,
|
|
31516
|
+
this.maxBatchTextures + i
|
|
31517
|
+
);
|
|
31518
|
+
}
|
|
31519
|
+
}
|
|
31520
|
+
/**
|
|
31521
|
+
* @ignore
|
|
31522
|
+
*/
|
|
31523
|
+
reset() {
|
|
31524
|
+
super.reset();
|
|
31525
|
+
this.bindNormalSamplers();
|
|
31526
|
+
this.boundNormalMaps.fill(null);
|
|
31527
|
+
this.normalMapTextures.clear();
|
|
31528
|
+
this._lightCount = 0;
|
|
31529
|
+
this.defaultShader.setUniform("uLightCount", 0);
|
|
31530
|
+
this.defaultShader.setUniform("uAmbient", [0, 0, 0]);
|
|
31531
|
+
}
|
|
31532
|
+
/**
|
|
31533
|
+
* Upload per-frame Light2d uniforms used by the lit fragment path.
|
|
31534
|
+
* Called once per camera per frame (before the world tree walk).
|
|
31535
|
+
* Lights past `MAX_LIGHTS` are silently ignored.
|
|
31536
|
+
*
|
|
31537
|
+
* Coordinates must be supplied in the same space as the renderer's
|
|
31538
|
+
* pre-projection vertex coords (i.e. camera-local / FBO-local),
|
|
31539
|
+
* matching `Stage.drawLighting`'s convention.
|
|
31540
|
+
* @param {object} uniforms
|
|
31541
|
+
* @param {Float32Array} uniforms.positions - flat array of `[x, y, radius, intensity]` per light, length = 4 * count
|
|
31542
|
+
* @param {Float32Array} uniforms.colors - flat array of `[r, g, b]` per light, length = 3 * count
|
|
31543
|
+
* @param {Float32Array} [uniforms.heights] - flat array of per-light height, length = MAX_LIGHTS
|
|
31544
|
+
* @param {number} uniforms.count - number of lights to render (clamped to MAX_LIGHTS)
|
|
31545
|
+
* @param {number[]} [uniforms.ambient] - `[r, g, b]` ambient floor (0..1 each)
|
|
31546
|
+
*/
|
|
31547
|
+
setLightUniforms(uniforms) {
|
|
31548
|
+
const shader = this.defaultShader;
|
|
31549
|
+
const count = Math.min(uniforms.count | 0, this._maxLights);
|
|
31550
|
+
this._lightCount = count;
|
|
31551
|
+
shader.setUniform("uLightCount", count);
|
|
31552
|
+
if (count > 0) {
|
|
31553
|
+
shader.setUniform("uLightPos", uniforms.positions);
|
|
31554
|
+
shader.setUniform("uLightColor", uniforms.colors);
|
|
31555
|
+
if (uniforms.heights) {
|
|
31556
|
+
shader.setUniform("uLightHeight", uniforms.heights);
|
|
31557
|
+
}
|
|
31558
|
+
}
|
|
31559
|
+
if (uniforms.ambient) {
|
|
31560
|
+
shader.setUniform("uAmbient", uniforms.ambient);
|
|
31561
|
+
}
|
|
31562
|
+
}
|
|
31563
|
+
/**
|
|
31564
|
+
* Bind a normal-map image to the given GL texture unit. Uploads on
|
|
31565
|
+
* first use (via `uploadNormalMap`) and rebinds the cached
|
|
31566
|
+
* `WebGLTexture` on subsequent calls. Mirrors the
|
|
31567
|
+
* `bindTexture2D` / `createTexture2D` split used by `MaterialBatcher`,
|
|
31568
|
+
* but for normal-map textures which live outside the color
|
|
31569
|
+
* `TextureCache` (cached per-image in `normalMapTextures`).
|
|
31570
|
+
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} image - normal-map source
|
|
31571
|
+
* @param {number} unit - GL texture unit (already offset by `maxBatchTextures`)
|
|
31572
|
+
*/
|
|
31573
|
+
bindNormalMap(image, unit) {
|
|
31574
|
+
const cached = this.normalMapTextures.get(image);
|
|
31575
|
+
if (typeof cached !== "undefined") {
|
|
31576
|
+
this.bindTexture2D(cached, unit, false);
|
|
31577
|
+
return;
|
|
31578
|
+
}
|
|
31579
|
+
this.uploadNormalMap(image, unit);
|
|
31580
|
+
}
|
|
31581
|
+
/**
|
|
31582
|
+
* Upload a normal-map image to GL and cache the resulting `WebGLTexture`
|
|
31583
|
+
* for future `bindNormalMap` calls. Not meant to be called directly —
|
|
31584
|
+
* `bindNormalMap` invokes this on the first use of a given image.
|
|
31585
|
+
*
|
|
31586
|
+
* `premultipliedAlpha = false` — normal maps store linear-encoded
|
|
31587
|
+
* surface normals; multiplying through alpha would corrupt the
|
|
31588
|
+
* encoding for any non-opaque texel.
|
|
31589
|
+
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} image - normal-map source
|
|
31590
|
+
* @param {number} unit - GL texture unit (already offset by `maxBatchTextures`)
|
|
31591
|
+
*/
|
|
31592
|
+
uploadNormalMap(image, unit) {
|
|
31593
|
+
const gl = this.gl;
|
|
31594
|
+
this.createTexture2D(
|
|
31595
|
+
unit,
|
|
31596
|
+
image,
|
|
31597
|
+
this.renderer.settings.antiAlias ? gl.LINEAR : gl.NEAREST,
|
|
31598
|
+
"no-repeat",
|
|
31599
|
+
image.width,
|
|
31600
|
+
image.height,
|
|
31601
|
+
false,
|
|
31602
|
+
void 0,
|
|
31603
|
+
void 0,
|
|
31604
|
+
false
|
|
31605
|
+
);
|
|
31606
|
+
this.normalMapTextures.set(image, this.boundTextures[unit]);
|
|
31607
|
+
}
|
|
31608
|
+
/**
|
|
31609
|
+
* Add a textured quad with optional paired normal map.
|
|
31610
|
+
* @param {TextureAtlas} texture - Source texture atlas
|
|
31611
|
+
* @param {number} x - Destination x-coordinate
|
|
31612
|
+
* @param {number} y - Destination y-coordinate
|
|
31613
|
+
* @param {number} w - Destination width
|
|
31614
|
+
* @param {number} h - Destination height
|
|
31615
|
+
* @param {number} u0 - Texture UV (u0) value
|
|
31616
|
+
* @param {number} v0 - Texture UV (v0) value
|
|
31617
|
+
* @param {number} u1 - Texture UV (u1) value
|
|
31618
|
+
* @param {number} v1 - Texture UV (v1) value
|
|
31619
|
+
* @param {number} tint - tint color (UINT32 argb)
|
|
31620
|
+
* @param {boolean} [reupload=false] - Force the texture to be reuploaded
|
|
31621
|
+
* @param {HTMLImageElement|HTMLCanvasElement|null} [normalMap=null] - paired normal-map (SpriteIlluminator workflow)
|
|
31622
|
+
*/
|
|
31623
|
+
addQuad(texture, x, y, w, h, u0, v0, u1, v1, tint, reupload = false, normalMap = null) {
|
|
31624
|
+
const vertexData = this.vertexData;
|
|
31625
|
+
if (vertexData.isFull(4)) {
|
|
31626
|
+
this.flush();
|
|
31627
|
+
}
|
|
31628
|
+
let unit;
|
|
31629
|
+
if (this.useMultiTexture) {
|
|
31630
|
+
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
31631
|
+
if (unit >= this.maxBatchTextures) {
|
|
31632
|
+
this.flush();
|
|
31633
|
+
this.renderer.cache.resetUnitAssignments();
|
|
31634
|
+
this.boundNormalMaps.fill(null);
|
|
31635
|
+
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
31636
|
+
}
|
|
31637
|
+
} else {
|
|
31638
|
+
unit = this.uploadTexture(texture, w, h, reupload);
|
|
31639
|
+
if (unit !== this.currentSamplerUnit) {
|
|
31640
|
+
this.currentShader.setUniform("uSampler", unit);
|
|
31641
|
+
this.currentSamplerUnit = unit;
|
|
31642
|
+
}
|
|
31643
|
+
}
|
|
31644
|
+
let normalTextureId = -1;
|
|
31645
|
+
if (normalMap !== null && this.useMultiTexture) {
|
|
31646
|
+
const normalUnit = this.maxBatchTextures + unit;
|
|
31647
|
+
const prev = this.boundNormalMaps[unit];
|
|
31648
|
+
if (prev !== normalMap) {
|
|
31649
|
+
if (prev !== null) {
|
|
31650
|
+
this.flush();
|
|
31651
|
+
}
|
|
31652
|
+
this.bindNormalMap(normalMap, normalUnit);
|
|
31653
|
+
this.boundNormalMaps[unit] = normalMap;
|
|
31654
|
+
}
|
|
31655
|
+
normalTextureId = unit;
|
|
31656
|
+
}
|
|
31657
|
+
const m = this.viewMatrix;
|
|
31658
|
+
const vec0 = V_ARRAY[0].set(x, y);
|
|
31659
|
+
const vec1 = V_ARRAY[1].set(x + w, y);
|
|
31660
|
+
const vec2 = V_ARRAY[2].set(x, y + h);
|
|
31661
|
+
const vec3 = V_ARRAY[3].set(x + w, y + h);
|
|
31662
|
+
if (!m.isIdentity()) {
|
|
31663
|
+
m.apply(vec0);
|
|
31664
|
+
m.apply(vec1);
|
|
31665
|
+
m.apply(vec2);
|
|
31666
|
+
m.apply(vec3);
|
|
31667
|
+
}
|
|
31668
|
+
const textureId = this.useMultiTexture ? unit : 0;
|
|
31669
|
+
vertexData.push(vec0.x, vec0.y, u0, v0, tint, textureId, normalTextureId);
|
|
31670
|
+
vertexData.push(vec1.x, vec1.y, u1, v0, tint, textureId, normalTextureId);
|
|
31671
|
+
vertexData.push(vec2.x, vec2.y, u0, v1, tint, textureId, normalTextureId);
|
|
31672
|
+
vertexData.push(vec3.x, vec3.y, u1, v1, tint, textureId, normalTextureId);
|
|
31673
|
+
}
|
|
31674
|
+
/**
|
|
31675
|
+
* Override `blitTexture` so the FBO blit pushes `-1` as the unlit
|
|
31676
|
+
* sentinel (this batcher's vertex layout includes `aNormalTextureId`).
|
|
31677
|
+
* @param {WebGLTexture} source - the raw GL texture to blit
|
|
31678
|
+
* @param {number} x - destination x
|
|
31679
|
+
* @param {number} y - destination y
|
|
31680
|
+
* @param {number} width - destination width
|
|
31681
|
+
* @param {number} height - destination height
|
|
31682
|
+
* @param {GLShader|ShaderEffect} shader - the shader effect to apply
|
|
31683
|
+
*/
|
|
31684
|
+
blitTexture(source, x, y, width, height, shader) {
|
|
31685
|
+
const gl = this.gl;
|
|
31686
|
+
this.useShader(shader);
|
|
31687
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
31688
|
+
gl.bindTexture(gl.TEXTURE_2D, source);
|
|
31689
|
+
this.currentTextureUnit = 0;
|
|
31690
|
+
this.boundTextures[0] = source;
|
|
31691
|
+
shader.setUniform("uSampler", 0);
|
|
31692
|
+
const m = this.viewMatrix;
|
|
31693
|
+
const vec0 = V_ARRAY[0].set(x, y);
|
|
31694
|
+
const vec1 = V_ARRAY[1].set(x + width, y);
|
|
31695
|
+
const vec2 = V_ARRAY[2].set(x, y + height);
|
|
31696
|
+
const vec3 = V_ARRAY[3].set(x + width, y + height);
|
|
31697
|
+
if (m && !m.isIdentity()) {
|
|
31698
|
+
m.apply(vec0);
|
|
31699
|
+
m.apply(vec1);
|
|
31700
|
+
m.apply(vec2);
|
|
31701
|
+
m.apply(vec3);
|
|
31702
|
+
}
|
|
31703
|
+
const tint = 4294967295;
|
|
31704
|
+
this.vertexData.push(vec0.x, vec0.y, 0, 1, tint, 0, -1);
|
|
31705
|
+
this.vertexData.push(vec1.x, vec1.y, 1, 1, tint, 0, -1);
|
|
31706
|
+
this.vertexData.push(vec2.x, vec2.y, 0, 0, tint, 0, -1);
|
|
31707
|
+
this.vertexData.push(vec3.x, vec3.y, 1, 0, tint, 0, -1);
|
|
31708
|
+
this.flush();
|
|
31709
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
31710
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
31711
|
+
this.currentTextureUnit = -1;
|
|
31712
|
+
delete this.boundTextures[0];
|
|
31713
|
+
this.useShader(this.defaultShader);
|
|
31714
|
+
}
|
|
31715
|
+
};
|
|
31716
|
+
|
|
31717
|
+
// src/video/webgl/shaders/mesh.frag
|
|
31718
|
+
var mesh_default = "uniform sampler2D uSampler;\nvarying vec4 vColor;\nvarying vec2 vRegion;\n\nvoid main(void) {\n gl_FragColor = texture2D(uSampler, vRegion) * vColor;\n}\n";
|
|
31719
|
+
|
|
31720
|
+
// src/video/webgl/shaders/mesh.vert
|
|
31721
|
+
var mesh_default2 = "// Current vertex point (3D position for mesh rendering)\nattribute vec3 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\n\n// Projection matrix\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\n\nvoid main(void) {\n // Transform the vertex position by the projection matrix\n gl_Position = uProjectionMatrix * vec4(aVertex, 1.0);\n // Pass the remaining attributes to the fragment shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n}\n";
|
|
31722
|
+
|
|
30670
31723
|
// src/video/webgl/batchers/mesh_batcher.js
|
|
30671
31724
|
var _v = new Vector2d();
|
|
30672
31725
|
var MeshBatcher = class extends MaterialBatcher {
|
|
@@ -30940,241 +31993,122 @@ var PrimitiveBatcher = class extends Batcher {
|
|
|
30940
31993
|
}
|
|
30941
31994
|
};
|
|
30942
31995
|
|
|
30943
|
-
// src/video/webgl/
|
|
30944
|
-
|
|
30945
|
-
const count = Math.max(maxTextures, 1);
|
|
30946
|
-
const lines = [];
|
|
30947
|
-
for (let i = 0; i < count; i++) {
|
|
30948
|
-
lines.push("uniform sampler2D uSampler" + i + ";");
|
|
30949
|
-
}
|
|
30950
|
-
lines.push("varying vec4 vColor;");
|
|
30951
|
-
lines.push("varying vec2 vRegion;");
|
|
30952
|
-
lines.push("varying float vTextureId;");
|
|
30953
|
-
lines.push("");
|
|
30954
|
-
lines.push("void main(void) {");
|
|
30955
|
-
lines.push(" vec4 color;");
|
|
30956
|
-
for (let i = 0; i < count; i++) {
|
|
30957
|
-
if (i === 0) {
|
|
30958
|
-
lines.push(" if (vTextureId < 0.5) {");
|
|
30959
|
-
} else {
|
|
30960
|
-
lines.push(" } else if (vTextureId < " + (i + 0.5) + ") {");
|
|
30961
|
-
}
|
|
30962
|
-
lines.push(" color = texture2D(uSampler" + i + ", vRegion);");
|
|
30963
|
-
}
|
|
30964
|
-
lines.push(" } else {");
|
|
30965
|
-
lines.push(" color = texture2D(uSampler0, vRegion);");
|
|
30966
|
-
lines.push(" }");
|
|
30967
|
-
lines.push(" gl_FragColor = color * vColor;");
|
|
30968
|
-
lines.push("}");
|
|
30969
|
-
return lines.join("\n");
|
|
30970
|
-
}
|
|
30971
|
-
|
|
30972
|
-
// src/video/webgl/shaders/quad-multi.vert
|
|
30973
|
-
var quad_multi_default = "// Current vertex point\nattribute vec2 aVertex;\nattribute vec2 aRegion;\nattribute vec4 aColor;\nattribute float aTextureId;\n\n// Projection matrix\nuniform mat4 uProjectionMatrix;\n\nvarying vec2 vRegion;\nvarying vec4 vColor;\nvarying float vTextureId;\n\nvoid main(void) {\n // Transform the vertex position by the projection matrix\n gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0);\n // Pass the remaining attributes to the fragment shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n vTextureId = aTextureId;\n}\n";
|
|
30974
|
-
|
|
30975
|
-
// src/video/webgl/batchers/quad_batcher.js
|
|
30976
|
-
var V_ARRAY = [
|
|
30977
|
-
new Vector2d(),
|
|
30978
|
-
new Vector2d(),
|
|
30979
|
-
new Vector2d(),
|
|
30980
|
-
new Vector2d()
|
|
30981
|
-
];
|
|
30982
|
-
var QuadBatcher = class extends MaterialBatcher {
|
|
31996
|
+
// src/video/webgl/effects/radialGradient.js
|
|
31997
|
+
var RadialGradientEffect = class extends ShaderEffect {
|
|
30983
31998
|
/**
|
|
30984
|
-
*
|
|
30985
|
-
* @
|
|
31999
|
+
* @param {WebGLRenderer} renderer - the current renderer instance
|
|
32000
|
+
* @param {object} [options] - initial uniform values
|
|
32001
|
+
* @param {Color} [options.color] - center color (0..255 RGB); defaults to white
|
|
32002
|
+
* @param {number} [options.intensity=1] - peak alpha at the center (0..1+)
|
|
30986
32003
|
*/
|
|
30987
|
-
|
|
30988
|
-
|
|
30989
|
-
|
|
30990
|
-
|
|
30991
|
-
|
|
30992
|
-
|
|
30993
|
-
|
|
30994
|
-
|
|
30995
|
-
|
|
30996
|
-
|
|
30997
|
-
|
|
30998
|
-
|
|
30999
|
-
|
|
31000
|
-
|
|
31001
|
-
|
|
31002
|
-
|
|
31003
|
-
|
|
31004
|
-
|
|
31005
|
-
|
|
31006
|
-
|
|
31007
|
-
|
|
31008
|
-
|
|
31009
|
-
|
|
31010
|
-
|
|
31011
|
-
|
|
31012
|
-
{
|
|
31013
|
-
name: "aTextureId",
|
|
31014
|
-
size: 1,
|
|
31015
|
-
type: renderer2.gl.FLOAT,
|
|
31016
|
-
normalized: false,
|
|
31017
|
-
offset: 5 * Float32Array.BYTES_PER_ELEMENT
|
|
31018
|
-
}
|
|
31019
|
-
],
|
|
31020
|
-
shader: {
|
|
31021
|
-
vertex: quad_multi_default,
|
|
31022
|
-
fragment: buildMultiTextureFragment(this.maxBatchTextures)
|
|
31023
|
-
}
|
|
31024
|
-
});
|
|
31025
|
-
for (let i = 0; i < this.maxBatchTextures; i++) {
|
|
31026
|
-
this.defaultShader.setUniform("uSampler" + i, i);
|
|
31027
|
-
}
|
|
31028
|
-
this.useMultiTexture = true;
|
|
31029
|
-
const maxQuads = this.vertexData.maxVertex / 4;
|
|
31030
|
-
this.indexBuffer = new WebGLIndexBuffer(
|
|
31031
|
-
this.gl,
|
|
31032
|
-
maxQuads * 6,
|
|
31033
|
-
this.renderer.WebGLVersion > 1
|
|
31034
|
-
);
|
|
31035
|
-
this.indexBuffer.fillQuadPattern(maxQuads);
|
|
31036
|
-
}
|
|
31037
|
-
/**
|
|
31038
|
-
* Select the shader to use for compositing.
|
|
31039
|
-
* Multi-texture batching is automatically enabled when the default
|
|
31040
|
-
* shader is active, and disabled for custom ShaderEffect shaders.
|
|
31041
|
-
* @see GLShader
|
|
31042
|
-
* @see ShaderEffect
|
|
31043
|
-
* @param {GLShader|ShaderEffect} shader - a reference to a GLShader or ShaderEffect instance
|
|
31044
|
-
*/
|
|
31045
|
-
useShader(shader) {
|
|
31046
|
-
super.useShader(shader);
|
|
31047
|
-
this.useMultiTexture = shader === this.defaultShader;
|
|
31048
|
-
}
|
|
31049
|
-
/**
|
|
31050
|
-
* Reset compositor internal state
|
|
31051
|
-
* @ignore
|
|
31052
|
-
*/
|
|
31053
|
-
reset() {
|
|
31054
|
-
super.reset();
|
|
31055
|
-
const maxQuads = this.vertexData.maxVertex / 4;
|
|
31056
|
-
this.indexBuffer = new WebGLIndexBuffer(
|
|
31057
|
-
this.gl,
|
|
31058
|
-
maxQuads * 6,
|
|
31059
|
-
this.renderer.WebGLVersion > 1
|
|
32004
|
+
constructor(renderer2, options = {}) {
|
|
32005
|
+
super(
|
|
32006
|
+
renderer2,
|
|
32007
|
+
`
|
|
32008
|
+
uniform vec3 uColor;
|
|
32009
|
+
uniform float uIntensity;
|
|
32010
|
+
vec4 apply(vec4 color, vec2 uv) {
|
|
32011
|
+
// recenter to [-1, 1] across the quad. The quad's own aspect
|
|
32012
|
+
// ratio handles elliptical falloffs naturally \u2014 length(c) == 1
|
|
32013
|
+
// lies on the inscribed ellipse in world space.
|
|
32014
|
+
vec2 c = uv * 2.0 - 1.0;
|
|
32015
|
+
float d = length(c);
|
|
32016
|
+
// linear ramp matches Canvas createRadialGradient's two-stop output
|
|
32017
|
+
float f = clamp(1.0 - d, 0.0, 1.0);
|
|
32018
|
+
// 'color' is the per-vertex tint, already premultiplied by
|
|
32019
|
+
// alpha in the vertex shader (vColor = vec4(aColor.bgr *
|
|
32020
|
+
// aColor.a, aColor.a)). For standalone use the tint is
|
|
32021
|
+
// (1,1,1,1) and the uniforms drive the look; for the Light2d
|
|
32022
|
+
// batching path the uniforms stay at default and the tint
|
|
32023
|
+
// carries the per-light color + intensity.
|
|
32024
|
+
vec3 rgb = color.rgb * uColor * uIntensity * f;
|
|
32025
|
+
float a = color.a * uIntensity * f;
|
|
32026
|
+
return vec4(rgb, a);
|
|
32027
|
+
}
|
|
32028
|
+
`
|
|
31060
32029
|
);
|
|
31061
|
-
this.
|
|
31062
|
-
|
|
31063
|
-
|
|
32030
|
+
this._colorBuf = new Float32Array(3);
|
|
32031
|
+
const color = options.color;
|
|
32032
|
+
if (color) {
|
|
32033
|
+
this.setColor(color);
|
|
32034
|
+
} else {
|
|
32035
|
+
this._colorBuf[0] = 1;
|
|
32036
|
+
this._colorBuf[1] = 1;
|
|
32037
|
+
this._colorBuf[2] = 1;
|
|
32038
|
+
this.setUniform("uColor", this._colorBuf);
|
|
31064
32039
|
}
|
|
31065
|
-
this.
|
|
32040
|
+
this.setIntensity(options.intensity ?? 1);
|
|
31066
32041
|
}
|
|
31067
32042
|
/**
|
|
31068
|
-
*
|
|
31069
|
-
*
|
|
32043
|
+
* Set the center color. RGB only — alpha is ignored (the radial
|
|
32044
|
+
* falloff supplies the per-pixel alpha).
|
|
32045
|
+
* @param {Color} color - 0..255 RGB color
|
|
31070
32046
|
*/
|
|
31071
|
-
|
|
31072
|
-
|
|
31073
|
-
|
|
31074
|
-
|
|
31075
|
-
|
|
31076
|
-
const vertexSize = vertex.vertexSize;
|
|
31077
|
-
this.indexBuffer.bind();
|
|
31078
|
-
if (this.renderer.WebGLVersion > 1) {
|
|
31079
|
-
gl.bufferData(
|
|
31080
|
-
gl.ARRAY_BUFFER,
|
|
31081
|
-
vertex.toFloat32(),
|
|
31082
|
-
gl.STREAM_DRAW,
|
|
31083
|
-
0,
|
|
31084
|
-
vertexCount * vertexSize
|
|
31085
|
-
);
|
|
31086
|
-
} else {
|
|
31087
|
-
gl.bufferData(
|
|
31088
|
-
gl.ARRAY_BUFFER,
|
|
31089
|
-
vertex.toFloat32(0, vertexCount * vertexSize),
|
|
31090
|
-
gl.STREAM_DRAW
|
|
31091
|
-
);
|
|
31092
|
-
}
|
|
31093
|
-
const indexCount = vertexCount / 4 * 6;
|
|
31094
|
-
gl.drawElements(mode, indexCount, this.indexBuffer.type, 0);
|
|
31095
|
-
vertex.clear();
|
|
31096
|
-
}
|
|
32047
|
+
setColor(color) {
|
|
32048
|
+
this._colorBuf[0] = color.r / 255;
|
|
32049
|
+
this._colorBuf[1] = color.g / 255;
|
|
32050
|
+
this._colorBuf[2] = color.b / 255;
|
|
32051
|
+
this.setUniform("uColor", this._colorBuf);
|
|
31097
32052
|
}
|
|
31098
32053
|
/**
|
|
31099
|
-
*
|
|
31100
|
-
*
|
|
31101
|
-
*
|
|
31102
|
-
* @param {WebGLTexture} source - the raw GL texture to blit
|
|
31103
|
-
* @param {number} x - destination x
|
|
31104
|
-
* @param {number} y - destination y
|
|
31105
|
-
* @param {number} width - destination width
|
|
31106
|
-
* @param {number} height - destination height
|
|
31107
|
-
* @param {GLShader|ShaderEffect} shader - the shader effect to apply
|
|
32054
|
+
* Set the peak intensity. Acts as a brightness multiplier on the
|
|
32055
|
+
* falloff curve; values above 1 over-saturate the center of the gradient.
|
|
32056
|
+
* @param {number} intensity - 0..1+ multiplier
|
|
31108
32057
|
*/
|
|
31109
|
-
|
|
31110
|
-
|
|
31111
|
-
this.useShader(shader);
|
|
31112
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
31113
|
-
gl.bindTexture(gl.TEXTURE_2D, source);
|
|
31114
|
-
shader.setUniform("uSampler", 0);
|
|
31115
|
-
const tint = 4294967295;
|
|
31116
|
-
this.vertexData.push(x, y, 0, 1, tint, 0);
|
|
31117
|
-
this.vertexData.push(x + width, y, 1, 1, tint, 0);
|
|
31118
|
-
this.vertexData.push(x, y + height, 0, 0, tint, 0);
|
|
31119
|
-
this.vertexData.push(x + width, y + height, 1, 0, tint, 0);
|
|
31120
|
-
this.flush();
|
|
31121
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
31122
|
-
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
31123
|
-
delete this.boundTextures[0];
|
|
31124
|
-
this.useShader(this.defaultShader);
|
|
32058
|
+
setIntensity(intensity) {
|
|
32059
|
+
this.setUniform("uIntensity", intensity);
|
|
31125
32060
|
}
|
|
31126
|
-
|
|
31127
|
-
|
|
31128
|
-
|
|
31129
|
-
|
|
31130
|
-
|
|
31131
|
-
|
|
31132
|
-
|
|
31133
|
-
|
|
31134
|
-
|
|
31135
|
-
|
|
31136
|
-
|
|
31137
|
-
|
|
31138
|
-
|
|
31139
|
-
|
|
31140
|
-
|
|
31141
|
-
|
|
31142
|
-
|
|
31143
|
-
|
|
31144
|
-
|
|
31145
|
-
|
|
31146
|
-
if (this.useMultiTexture) {
|
|
31147
|
-
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
31148
|
-
if (unit >= this.maxBatchTextures) {
|
|
31149
|
-
this.flush();
|
|
31150
|
-
this.renderer.cache.resetUnitAssignments();
|
|
31151
|
-
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
31152
|
-
}
|
|
31153
|
-
} else {
|
|
31154
|
-
unit = this.uploadTexture(texture, w, h, reupload);
|
|
31155
|
-
if (unit !== this.currentSamplerUnit) {
|
|
31156
|
-
this.currentShader.setUniform("uSampler", unit);
|
|
31157
|
-
this.currentSamplerUnit = unit;
|
|
32061
|
+
};
|
|
32062
|
+
|
|
32063
|
+
// src/video/webgl/lighting/pack.ts
|
|
32064
|
+
function createLightUniformScratch() {
|
|
32065
|
+
return {
|
|
32066
|
+
positions: new Float32Array(MAX_LIGHTS * 4),
|
|
32067
|
+
colors: new Float32Array(MAX_LIGHTS * 3),
|
|
32068
|
+
heights: new Float32Array(MAX_LIGHTS),
|
|
32069
|
+
ambient: [0, 0, 0]
|
|
32070
|
+
};
|
|
32071
|
+
}
|
|
32072
|
+
function packLights(lights, ambient, translateX, translateY, scratch) {
|
|
32073
|
+
scratch.positions.fill(0);
|
|
32074
|
+
scratch.colors.fill(0);
|
|
32075
|
+
scratch.heights.fill(0);
|
|
32076
|
+
let i = 0;
|
|
32077
|
+
if (lights) {
|
|
32078
|
+
for (const light of lights) {
|
|
32079
|
+
if (i >= MAX_LIGHTS) {
|
|
32080
|
+
break;
|
|
31158
32081
|
}
|
|
32082
|
+
const b = light.getBounds();
|
|
32083
|
+
const radius = Math.max(b.width, b.height) / 2;
|
|
32084
|
+
scratch.positions[i * 4 + 0] = b.centerX - translateX;
|
|
32085
|
+
scratch.positions[i * 4 + 1] = b.centerY - translateY;
|
|
32086
|
+
scratch.positions[i * 4 + 2] = radius;
|
|
32087
|
+
scratch.positions[i * 4 + 3] = light.intensity;
|
|
32088
|
+
scratch.colors[i * 3 + 0] = light.color.r / 255;
|
|
32089
|
+
scratch.colors[i * 3 + 1] = light.color.g / 255;
|
|
32090
|
+
scratch.colors[i * 3 + 2] = light.color.b / 255;
|
|
32091
|
+
scratch.heights[i] = light.lightHeight;
|
|
32092
|
+
i++;
|
|
31159
32093
|
}
|
|
31160
|
-
const m = this.viewMatrix;
|
|
31161
|
-
const vec0 = V_ARRAY[0].set(x, y);
|
|
31162
|
-
const vec1 = V_ARRAY[1].set(x + w, y);
|
|
31163
|
-
const vec2 = V_ARRAY[2].set(x, y + h);
|
|
31164
|
-
const vec3 = V_ARRAY[3].set(x + w, y + h);
|
|
31165
|
-
if (!m.isIdentity()) {
|
|
31166
|
-
m.apply(vec0);
|
|
31167
|
-
m.apply(vec1);
|
|
31168
|
-
m.apply(vec2);
|
|
31169
|
-
m.apply(vec3);
|
|
31170
|
-
}
|
|
31171
|
-
const textureId = this.useMultiTexture ? unit : 0;
|
|
31172
|
-
vertexData.push(vec0.x, vec0.y, u0, v0, tint, textureId);
|
|
31173
|
-
vertexData.push(vec1.x, vec1.y, u1, v0, tint, textureId);
|
|
31174
|
-
vertexData.push(vec2.x, vec2.y, u0, v1, tint, textureId);
|
|
31175
|
-
vertexData.push(vec3.x, vec3.y, u1, v1, tint, textureId);
|
|
31176
32094
|
}
|
|
31177
|
-
|
|
32095
|
+
if (ambient) {
|
|
32096
|
+
scratch.ambient[0] = ambient.r / 255;
|
|
32097
|
+
scratch.ambient[1] = ambient.g / 255;
|
|
32098
|
+
scratch.ambient[2] = ambient.b / 255;
|
|
32099
|
+
} else {
|
|
32100
|
+
scratch.ambient[0] = 0;
|
|
32101
|
+
scratch.ambient[1] = 0;
|
|
32102
|
+
scratch.ambient[2] = 0;
|
|
32103
|
+
}
|
|
32104
|
+
return {
|
|
32105
|
+
positions: scratch.positions,
|
|
32106
|
+
colors: scratch.colors,
|
|
32107
|
+
heights: scratch.heights,
|
|
32108
|
+
ambient: scratch.ambient,
|
|
32109
|
+
count: i
|
|
32110
|
+
};
|
|
32111
|
+
}
|
|
31178
32112
|
|
|
31179
32113
|
// src/video/webgl/webgl_renderer.js
|
|
31180
32114
|
var _tempMatrix = new Matrix3d();
|
|
@@ -31205,6 +32139,7 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31205
32139
|
this._rectTriangles = Array.from({ length: 6 }, () => {
|
|
31206
32140
|
return { x: 0, y: 0 };
|
|
31207
32141
|
});
|
|
32142
|
+
this._clipAABB = new Bounds();
|
|
31208
32143
|
this._polyVerts = [];
|
|
31209
32144
|
this._currentGradient = null;
|
|
31210
32145
|
this.currentTransform = this.renderState.currentTransform;
|
|
@@ -31214,6 +32149,9 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31214
32149
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
31215
32150
|
const CustomBatcher = this.settings.batcher || this.settings.compositor;
|
|
31216
32151
|
this.addBatcher(new (CustomBatcher || QuadBatcher)(this), "quad", true);
|
|
32152
|
+
if (!CustomBatcher) {
|
|
32153
|
+
this.addBatcher(new LitQuadBatcher(this), "litQuad");
|
|
32154
|
+
}
|
|
31217
32155
|
this.addBatcher(new (CustomBatcher || PrimitiveBatcher)(this), "primitive");
|
|
31218
32156
|
this.addBatcher(new MeshBatcher(this), "mesh");
|
|
31219
32157
|
this.gl.disable(this.gl.DEPTH_TEST);
|
|
@@ -31332,6 +32270,16 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31332
32270
|
this.gl.disable(this.gl.SCISSOR_TEST);
|
|
31333
32271
|
this._scissorActive = false;
|
|
31334
32272
|
this._renderTargetPool.destroy();
|
|
32273
|
+
if (this._lightShader !== void 0) {
|
|
32274
|
+
this._lightShader.destroy?.();
|
|
32275
|
+
this._lightShader = void 0;
|
|
32276
|
+
}
|
|
32277
|
+
if (this._lightAtlas !== void 0) {
|
|
32278
|
+
this._lightAtlas.sources.forEach((source) => {
|
|
32279
|
+
this.cache.delete?.(source);
|
|
32280
|
+
});
|
|
32281
|
+
this._lightAtlas = void 0;
|
|
32282
|
+
}
|
|
31335
32283
|
}
|
|
31336
32284
|
/**
|
|
31337
32285
|
* add a new batcher to this renderer
|
|
@@ -31359,21 +32307,21 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31359
32307
|
if (typeof batcher === "undefined") {
|
|
31360
32308
|
throw new Error("Invalid Batcher");
|
|
31361
32309
|
}
|
|
31362
|
-
|
|
32310
|
+
const targetShader = shader != null ? shader : batcher.defaultShader;
|
|
32311
|
+
if (this.currentBatcher === batcher && batcher.currentShader === targetShader) {
|
|
31363
32312
|
return this.currentBatcher;
|
|
31364
32313
|
}
|
|
31365
32314
|
if (this.currentBatcher !== batcher) {
|
|
31366
32315
|
if (this.currentBatcher !== void 0) {
|
|
31367
32316
|
this.currentBatcher.flush();
|
|
32317
|
+
this.currentBatcher.unbind();
|
|
31368
32318
|
}
|
|
31369
32319
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
31370
32320
|
this.currentBatcher = batcher;
|
|
31371
32321
|
this.currentBatcher.bind();
|
|
31372
32322
|
this.currentBatcher.setProjection(this.projectionMatrix);
|
|
31373
32323
|
}
|
|
31374
|
-
|
|
31375
|
-
this.currentBatcher.useShader(shader);
|
|
31376
|
-
}
|
|
32324
|
+
this.currentBatcher.useShader(targetShader);
|
|
31377
32325
|
return this.currentBatcher;
|
|
31378
32326
|
}
|
|
31379
32327
|
/**
|
|
@@ -31418,6 +32366,109 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31418
32366
|
flush() {
|
|
31419
32367
|
this.currentBatcher.flush();
|
|
31420
32368
|
}
|
|
32369
|
+
/**
|
|
32370
|
+
* Upload per-frame Light2d uniforms used by the lit sprite pipeline.
|
|
32371
|
+
*
|
|
32372
|
+
* Packs the active lights into pre-allocated scratch buffers, then
|
|
32373
|
+
* forwards to `LitQuadBatcher`. Light positions are translated from
|
|
32374
|
+
* world-space (where `light.getBounds().centerX/Y` lives) into the
|
|
32375
|
+
* renderer's pre-projection coords by subtracting `(translateX, translateY)`,
|
|
32376
|
+
* matching what `Stage.drawLighting` does for the cutout pass — so
|
|
32377
|
+
* the lit fragment's `lightPos - vWorldPos` math lines up with the
|
|
32378
|
+
* camera's view.
|
|
32379
|
+
*
|
|
32380
|
+
* Lights past `MAX_LIGHTS` (8) are silently dropped. Also caches the
|
|
32381
|
+
* active light count on the renderer so `drawImage` can dispatch
|
|
32382
|
+
* normal-mapped sprites to the lit batcher only when there's
|
|
32383
|
+
* something to light them with.
|
|
32384
|
+
* @param {Iterable<object>} [lights] - active `Light2d` instances; falsy/empty no-ops the lit pipeline
|
|
32385
|
+
* @param {object} [ambient] - ambient lighting color (0..255 RGB); defaults to black
|
|
32386
|
+
* @param {number} [translateX=0] - world-to-screen X translate (matches `Camera2d.draw()`)
|
|
32387
|
+
* @param {number} [translateY=0] - world-to-screen Y translate
|
|
32388
|
+
*/
|
|
32389
|
+
setLightUniforms(lights, ambient, translateX = 0, translateY = 0) {
|
|
32390
|
+
if (this._lightUniformsScratch === void 0) {
|
|
32391
|
+
this._lightUniformsScratch = createLightUniformScratch();
|
|
32392
|
+
}
|
|
32393
|
+
const u = packLights(
|
|
32394
|
+
lights,
|
|
32395
|
+
ambient,
|
|
32396
|
+
translateX,
|
|
32397
|
+
translateY,
|
|
32398
|
+
this._lightUniformsScratch
|
|
32399
|
+
);
|
|
32400
|
+
this.activeLightCount = u.count;
|
|
32401
|
+
const lit = this.batchers.get("litQuad");
|
|
32402
|
+
if (lit && typeof lit.setLightUniforms === "function") {
|
|
32403
|
+
lit.setLightUniforms(u);
|
|
32404
|
+
if (this.currentBatcher && this.currentBatcher !== lit) {
|
|
32405
|
+
const shader = this.currentBatcher.currentShader || this.currentBatcher.defaultShader;
|
|
32406
|
+
if (shader) {
|
|
32407
|
+
this.gl.useProgram(shader.program);
|
|
32408
|
+
this.currentProgram = shader.program;
|
|
32409
|
+
}
|
|
32410
|
+
}
|
|
32411
|
+
}
|
|
32412
|
+
}
|
|
32413
|
+
/**
|
|
32414
|
+
* @inheritdoc
|
|
32415
|
+
*
|
|
32416
|
+
* Renders the light as a quad through a shared
|
|
32417
|
+
* {@link RadialGradientEffect} fragment shader (procedural — no
|
|
32418
|
+
* per-light texture). The shader and a shared 1×1 white-pixel atlas
|
|
32419
|
+
* are lazy-allocated on first call and reused for every Light2d on
|
|
32420
|
+
* this renderer. Each light's color and intensity are encoded into
|
|
32421
|
+
* the per-vertex tint so consecutive `drawLight` calls accumulate
|
|
32422
|
+
* into the quad batcher's buffer and flush together — N lights
|
|
32423
|
+
* become 1 program switch + 1 flush instead of 2N + N.
|
|
32424
|
+
* @param {object} light - the Light2d instance to render
|
|
32425
|
+
*/
|
|
32426
|
+
drawLight(light) {
|
|
32427
|
+
if (this._lightShader === void 0) {
|
|
32428
|
+
this._lightShader = new RadialGradientEffect(this);
|
|
32429
|
+
}
|
|
32430
|
+
const batcher = this.setBatcher("quad", this._lightShader);
|
|
32431
|
+
batcher.addQuad(
|
|
32432
|
+
this._getLightAtlas(),
|
|
32433
|
+
light.pos.x,
|
|
32434
|
+
light.pos.y,
|
|
32435
|
+
light.width,
|
|
32436
|
+
light.height,
|
|
32437
|
+
0,
|
|
32438
|
+
0,
|
|
32439
|
+
1,
|
|
32440
|
+
1,
|
|
32441
|
+
// pack the light's color (RGB) and intensity (A) into the
|
|
32442
|
+
// vertex tint — the shader's `apply()` reads `color.rgb` and
|
|
32443
|
+
// `color.a` as the per-light values.
|
|
32444
|
+
light.color.toUint32(light.intensity)
|
|
32445
|
+
);
|
|
32446
|
+
}
|
|
32447
|
+
/**
|
|
32448
|
+
* Lazy-init a shared 1×1 white `TextureAtlas` used as the source
|
|
32449
|
+
* texture for `drawLight`'s procedural shader. The shader ignores
|
|
32450
|
+
* the sampled color, but `addQuad`'s vertex format includes a
|
|
32451
|
+
* texture-unit attribute so we still need a real texture; sharing
|
|
32452
|
+
* one across every light keeps them on the same multi-texture slot
|
|
32453
|
+
* (no flush on light switch).
|
|
32454
|
+
* @returns {TextureAtlas}
|
|
32455
|
+
* @ignore
|
|
32456
|
+
*/
|
|
32457
|
+
_getLightAtlas() {
|
|
32458
|
+
if (this._lightAtlas === void 0) {
|
|
32459
|
+
const canvas = globalThis.document ? globalThis.document.createElement("canvas") : new OffscreenCanvas(1, 1);
|
|
32460
|
+
canvas.width = 1;
|
|
32461
|
+
canvas.height = 1;
|
|
32462
|
+
const ctx = canvas.getContext("2d");
|
|
32463
|
+
ctx.fillStyle = "#fff";
|
|
32464
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
32465
|
+
this._lightAtlas = new TextureAtlas(
|
|
32466
|
+
createAtlas(1, 1, "lightWhite", "no-repeat"),
|
|
32467
|
+
canvas
|
|
32468
|
+
);
|
|
32469
|
+
}
|
|
32470
|
+
return this._lightAtlas;
|
|
32471
|
+
}
|
|
31421
32472
|
/**
|
|
31422
32473
|
* Begin capturing rendering to an offscreen FBO for post-effect processing.
|
|
31423
32474
|
* @param {Renderable} renderable - the renderable requesting post-effect processing
|
|
@@ -31602,19 +32653,22 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31602
32653
|
*/
|
|
31603
32654
|
enableScissor(x, y, width, height) {
|
|
31604
32655
|
const gl = this.gl;
|
|
32656
|
+
const canvas = this.getCanvas();
|
|
32657
|
+
const aabb = this._clipAABB;
|
|
32658
|
+
aabb.clear();
|
|
32659
|
+
aabb.addFrame(x, y, x + width, y + height, this.currentTransform);
|
|
32660
|
+
const sx = Math.floor(aabb.min.x);
|
|
32661
|
+
const sy = Math.floor(aabb.min.y);
|
|
32662
|
+
const sw = Math.ceil(aabb.max.x - sx);
|
|
32663
|
+
const sh = Math.ceil(aabb.max.y - sy);
|
|
31605
32664
|
this.flush();
|
|
31606
32665
|
gl.enable(gl.SCISSOR_TEST);
|
|
31607
32666
|
this._scissorActive = true;
|
|
31608
|
-
gl.scissor(
|
|
31609
|
-
|
|
31610
|
-
|
|
31611
|
-
|
|
31612
|
-
|
|
31613
|
-
);
|
|
31614
|
-
this.currentScissor[0] = x;
|
|
31615
|
-
this.currentScissor[1] = y;
|
|
31616
|
-
this.currentScissor[2] = width;
|
|
31617
|
-
this.currentScissor[3] = height;
|
|
32667
|
+
gl.scissor(sx, canvas.height - sh - sy, sw, sh);
|
|
32668
|
+
this.currentScissor[0] = sx;
|
|
32669
|
+
this.currentScissor[1] = sy;
|
|
32670
|
+
this.currentScissor[2] = sw;
|
|
32671
|
+
this.currentScissor[3] = sh;
|
|
31618
32672
|
}
|
|
31619
32673
|
/**
|
|
31620
32674
|
* Disable the scissor test, allowing rendering to the full viewport.
|
|
@@ -31721,27 +32775,45 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31721
32775
|
dx |= 0;
|
|
31722
32776
|
dy |= 0;
|
|
31723
32777
|
}
|
|
31724
|
-
this.
|
|
32778
|
+
const useLit = this.batchers.has("litQuad") && this.activeLightCount > 0 && this.currentNormalMap !== null;
|
|
32779
|
+
this.setBatcher(useLit ? "litQuad" : "quad");
|
|
31725
32780
|
const shader = this.customShader;
|
|
31726
|
-
if (
|
|
32781
|
+
if (shader != null) {
|
|
31727
32782
|
this.currentBatcher.useShader(shader);
|
|
31728
32783
|
}
|
|
31729
32784
|
const reupload = typeof image.videoWidth !== "undefined";
|
|
31730
32785
|
const texture = this.cache.get(image);
|
|
31731
32786
|
const uvs = texture.getUVs(sx, sy, sw, sh);
|
|
31732
|
-
|
|
31733
|
-
|
|
31734
|
-
|
|
31735
|
-
|
|
31736
|
-
|
|
31737
|
-
|
|
31738
|
-
|
|
31739
|
-
|
|
31740
|
-
|
|
31741
|
-
|
|
31742
|
-
|
|
31743
|
-
|
|
31744
|
-
|
|
32787
|
+
if (useLit) {
|
|
32788
|
+
this.currentBatcher.addQuad(
|
|
32789
|
+
texture,
|
|
32790
|
+
dx,
|
|
32791
|
+
dy,
|
|
32792
|
+
dw,
|
|
32793
|
+
dh,
|
|
32794
|
+
uvs[0],
|
|
32795
|
+
uvs[1],
|
|
32796
|
+
uvs[2],
|
|
32797
|
+
uvs[3],
|
|
32798
|
+
this.currentTint.toUint32(this.getGlobalAlpha()),
|
|
32799
|
+
reupload,
|
|
32800
|
+
this.currentNormalMap
|
|
32801
|
+
);
|
|
32802
|
+
} else {
|
|
32803
|
+
this.currentBatcher.addQuad(
|
|
32804
|
+
texture,
|
|
32805
|
+
dx,
|
|
32806
|
+
dy,
|
|
32807
|
+
dw,
|
|
32808
|
+
dh,
|
|
32809
|
+
uvs[0],
|
|
32810
|
+
uvs[1],
|
|
32811
|
+
uvs[2],
|
|
32812
|
+
uvs[3],
|
|
32813
|
+
this.currentTint.toUint32(this.getGlobalAlpha()),
|
|
32814
|
+
reupload
|
|
32815
|
+
);
|
|
32816
|
+
}
|
|
31745
32817
|
if (typeof shader === "object") {
|
|
31746
32818
|
this.currentBatcher.useShader(this.currentBatcher.defaultShader);
|
|
31747
32819
|
}
|
|
@@ -31781,7 +32853,7 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31781
32853
|
drawMesh(mesh) {
|
|
31782
32854
|
const gl = this.gl;
|
|
31783
32855
|
this.setBatcher("mesh");
|
|
31784
|
-
if (
|
|
32856
|
+
if (this.customShader != null) {
|
|
31785
32857
|
this.currentBatcher.useShader(this.customShader);
|
|
31786
32858
|
}
|
|
31787
32859
|
gl.enable(gl.DEPTH_TEST);
|
|
@@ -31806,7 +32878,7 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31806
32878
|
gl.enable(gl.BLEND);
|
|
31807
32879
|
gl.disable(gl.DEPTH_TEST);
|
|
31808
32880
|
gl.depthMask(false);
|
|
31809
|
-
if (
|
|
32881
|
+
if (this.customShader != null) {
|
|
31810
32882
|
this.currentBatcher.useShader(this.currentBatcher.defaultShader);
|
|
31811
32883
|
}
|
|
31812
32884
|
}
|
|
@@ -32041,23 +33113,33 @@ var WebGLRenderer = class extends Renderer {
|
|
|
32041
33113
|
*/
|
|
32042
33114
|
restore() {
|
|
32043
33115
|
const canvas = this.getCanvas();
|
|
33116
|
+
const peek = this.renderState.peekScissor();
|
|
33117
|
+
const cur = this.currentScissor;
|
|
33118
|
+
const curActive = this._scissorActive === true;
|
|
33119
|
+
const willBeActive = peek !== null;
|
|
33120
|
+
const scissorChanging = curActive !== willBeActive || willBeActive && (cur[0] !== peek[0] || cur[1] !== peek[1] || cur[2] !== peek[2] || cur[3] !== peek[3]);
|
|
33121
|
+
if (scissorChanging) {
|
|
33122
|
+
this.flush();
|
|
33123
|
+
}
|
|
32044
33124
|
const result = this.renderState.restore(canvas.width, canvas.height);
|
|
32045
33125
|
if (result !== null) {
|
|
32046
33126
|
this.setBlendMode(result.blendMode);
|
|
32047
|
-
if (
|
|
33127
|
+
if (scissorChanging) {
|
|
32048
33128
|
const gl = this.gl;
|
|
32049
|
-
|
|
32050
|
-
|
|
32051
|
-
|
|
32052
|
-
|
|
32053
|
-
|
|
32054
|
-
|
|
32055
|
-
|
|
32056
|
-
|
|
32057
|
-
|
|
32058
|
-
|
|
32059
|
-
|
|
32060
|
-
|
|
33129
|
+
if (result.scissorActive) {
|
|
33130
|
+
const next = this.currentScissor;
|
|
33131
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
33132
|
+
this._scissorActive = true;
|
|
33133
|
+
gl.scissor(
|
|
33134
|
+
next[0],
|
|
33135
|
+
canvas.height - next[3] - next[1],
|
|
33136
|
+
next[2],
|
|
33137
|
+
next[3]
|
|
33138
|
+
);
|
|
33139
|
+
} else {
|
|
33140
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
33141
|
+
this._scissorActive = false;
|
|
33142
|
+
}
|
|
32061
33143
|
}
|
|
32062
33144
|
}
|
|
32063
33145
|
this._currentGradient = this.renderState.currentGradient;
|
|
@@ -32726,31 +33808,42 @@ var WebGLRenderer = class extends Renderer {
|
|
|
32726
33808
|
clipRect(x, y, width, height) {
|
|
32727
33809
|
const canvas = this.getCanvas();
|
|
32728
33810
|
const gl = this.gl;
|
|
32729
|
-
|
|
32730
|
-
|
|
33811
|
+
const m = this.currentTransform;
|
|
33812
|
+
if (!Number.isFinite(m.tx) || !Number.isFinite(m.ty)) {
|
|
32731
33813
|
if (this._scissorActive) {
|
|
32732
|
-
|
|
32733
|
-
|
|
32734
|
-
|
|
33814
|
+
this.flush();
|
|
33815
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
33816
|
+
this._scissorActive = false;
|
|
32735
33817
|
}
|
|
32736
|
-
|
|
32737
|
-
|
|
32738
|
-
|
|
32739
|
-
|
|
32740
|
-
|
|
32741
|
-
|
|
32742
|
-
|
|
32743
|
-
|
|
32744
|
-
|
|
32745
|
-
|
|
32746
|
-
|
|
32747
|
-
|
|
32748
|
-
|
|
32749
|
-
|
|
32750
|
-
|
|
32751
|
-
|
|
32752
|
-
|
|
33818
|
+
return;
|
|
33819
|
+
}
|
|
33820
|
+
const aabb = this._clipAABB;
|
|
33821
|
+
aabb.clear();
|
|
33822
|
+
aabb.addFrame(x, y, x + width, y + height, m);
|
|
33823
|
+
const sx = Math.floor(aabb.min.x);
|
|
33824
|
+
const sy = Math.floor(aabb.min.y);
|
|
33825
|
+
const sw = Math.ceil(aabb.max.x - sx);
|
|
33826
|
+
const sh = Math.ceil(aabb.max.y - sy);
|
|
33827
|
+
if (sx <= 0 && sy <= 0 && sx + sw >= canvas.width && sy + sh >= canvas.height) {
|
|
33828
|
+
if (this._scissorActive) {
|
|
33829
|
+
this.flush();
|
|
33830
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
33831
|
+
this._scissorActive = false;
|
|
33832
|
+
}
|
|
33833
|
+
return;
|
|
33834
|
+
}
|
|
33835
|
+
const cs = this.currentScissor;
|
|
33836
|
+
if (this._scissorActive && cs[0] === sx && cs[1] === sy && cs[2] === sw && cs[3] === sh) {
|
|
33837
|
+
return;
|
|
32753
33838
|
}
|
|
33839
|
+
this.flush();
|
|
33840
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
33841
|
+
this._scissorActive = true;
|
|
33842
|
+
gl.scissor(sx, canvas.height - sh - sy, sw, sh);
|
|
33843
|
+
cs[0] = sx;
|
|
33844
|
+
cs[1] = sy;
|
|
33845
|
+
cs[2] = sw;
|
|
33846
|
+
cs[3] = sh;
|
|
32754
33847
|
}
|
|
32755
33848
|
/**
|
|
32756
33849
|
* A mask limits rendering elements to the shape and position of the given mask object.
|
|
@@ -32769,15 +33862,16 @@ var WebGLRenderer = class extends Renderer {
|
|
|
32769
33862
|
}
|
|
32770
33863
|
this.maskLevel++;
|
|
32771
33864
|
gl.colorMask(false, false, false, false);
|
|
32772
|
-
gl.
|
|
32773
|
-
gl.
|
|
33865
|
+
gl.stencilMask(255);
|
|
33866
|
+
gl.stencilFunc(gl.ALWAYS, 0, 255);
|
|
33867
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
|
|
32774
33868
|
this.fill(mask);
|
|
32775
33869
|
this.flush();
|
|
32776
33870
|
gl.colorMask(true, true, true, true);
|
|
32777
33871
|
if (invert === true) {
|
|
32778
|
-
gl.stencilFunc(gl.EQUAL,
|
|
33872
|
+
gl.stencilFunc(gl.EQUAL, 0, 255);
|
|
32779
33873
|
} else {
|
|
32780
|
-
gl.stencilFunc(gl.
|
|
33874
|
+
gl.stencilFunc(gl.EQUAL, this.maskLevel, 255);
|
|
32781
33875
|
}
|
|
32782
33876
|
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
|
32783
33877
|
}
|