melonjs 19.2.0 → 19.4.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 +13 -10
- package/build/application/application.d.ts.map +1 -1
- package/build/application/defaultApplicationSettings.d.ts +1 -0
- package/build/application/defaultApplicationSettings.d.ts.map +1 -1
- package/build/application/settings.d.ts +12 -0
- package/build/application/settings.d.ts.map +1 -1
- package/build/camera/camera2d.d.ts.map +1 -1
- package/build/index.js +2665 -629
- package/build/index.js.map +4 -4
- package/build/input/pointerevent.d.ts.map +1 -1
- package/build/level/tiled/TMXLayer.d.ts +72 -5
- package/build/level/tiled/TMXLayer.d.ts.map +1 -1
- package/build/level/tiled/TMXTile.d.ts +19 -1
- package/build/level/tiled/TMXTile.d.ts.map +1 -1
- package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
- package/build/level/tiled/TMXTileset.d.ts +15 -0
- package/build/level/tiled/TMXTileset.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts +7 -1
- package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts +7 -1
- package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts +7 -1
- package/build/level/tiled/renderer/TMXOrthogonalRenderer.d.ts.map +1 -1
- package/build/physics/bounds.d.ts +5 -2
- package/build/physics/bounds.d.ts.map +1 -1
- package/build/physics/sat.d.ts.map +1 -1
- package/build/physics/world.d.ts +16 -0
- package/build/physics/world.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/imagelayer.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/utils/function.d.ts +1 -1
- package/build/utils/function.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 +77 -1
- 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/texture/cache.d.ts.map +1 -1
- package/build/video/texture/resource.d.ts +113 -0
- package/build/video/texture/resource.d.ts.map +1 -0
- 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/material_batcher.d.ts +8 -1
- package/build/video/webgl/batchers/material_batcher.d.ts.map +1 -1
- 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/renderers/tmxlayer/orthogonal.d.ts +108 -0
- package/build/video/webgl/renderers/tmxlayer/orthogonal.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/utils/attributes.d.ts +7 -0
- package/build/video/webgl/utils/attributes.d.ts.map +1 -1
- package/build/video/webgl/utils/precision.d.ts +1 -1
- package/build/video/webgl/utils/precision.d.ts.map +1 -1
- package/build/video/webgl/utils/uniforms.d.ts +13 -0
- package/build/video/webgl/utils/uniforms.d.ts.map +1 -1
- package/build/video/webgl/webgl_renderer.d.ts +36 -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.4.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;
|
|
@@ -6227,6 +6249,7 @@ var defaultApplicationSettings = {
|
|
|
6227
6249
|
consoleHeader: true,
|
|
6228
6250
|
blendMode: "normal",
|
|
6229
6251
|
physic: "builtin",
|
|
6252
|
+
gpuTilemap: true,
|
|
6230
6253
|
failIfMajorPerformanceCaveat: true,
|
|
6231
6254
|
highPrecisionShader: true,
|
|
6232
6255
|
subPixel: false,
|
|
@@ -14138,10 +14161,10 @@ var Container = class _Container extends Renderable {
|
|
|
14138
14161
|
draw(renderer2, viewport) {
|
|
14139
14162
|
const bounds = this.getBounds();
|
|
14140
14163
|
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
14164
|
renderer2.translate(this.pos.x, this.pos.y);
|
|
14165
|
+
if (this.root === false && this.clipping === true && bounds.isFinite() === true && Number.isFinite(this.width) === true && Number.isFinite(this.height) === true) {
|
|
14166
|
+
renderer2.clipRect(0, 0, this.width, this.height);
|
|
14167
|
+
}
|
|
14145
14168
|
if (this.backgroundColor.alpha > 1 / 255) {
|
|
14146
14169
|
renderer2.clearColor(this.backgroundColor);
|
|
14147
14170
|
}
|
|
@@ -15366,6 +15389,32 @@ var RenderState = class {
|
|
|
15366
15389
|
}
|
|
15367
15390
|
this._stackDepth = depth + 1;
|
|
15368
15391
|
}
|
|
15392
|
+
/**
|
|
15393
|
+
* Inspect the scissor box that the next `restore()` would install,
|
|
15394
|
+
* without mutating any state. Lets renderers detect whether a
|
|
15395
|
+
* pending `restore()` will actually change the scissor (and
|
|
15396
|
+
* decide, e.g., whether to flush GPU work first).
|
|
15397
|
+
*
|
|
15398
|
+
* Returns:
|
|
15399
|
+
* - `Int32Array` (length 4) — `[x, y, width, height]` of the
|
|
15400
|
+
* saved scissor when scissor was active at the matching `save()`
|
|
15401
|
+
* call.
|
|
15402
|
+
* - `null` — when the saved state had scissor disabled, or the
|
|
15403
|
+
* stack is empty. Treat this as "next scissor will be inactive".
|
|
15404
|
+
*
|
|
15405
|
+
* The returned array is a **live reference into the internal
|
|
15406
|
+
* stack** — zero allocation on a hot path. Callers MUST treat it
|
|
15407
|
+
* as read-only; mutating it corrupts subsequent `restore()` calls.
|
|
15408
|
+
* @ignore
|
|
15409
|
+
* @returns {Int32Array | null}
|
|
15410
|
+
*/
|
|
15411
|
+
peekScissor() {
|
|
15412
|
+
const depth = this._stackDepth - 1;
|
|
15413
|
+
if (depth < 0 || !this._scissorActive[depth]) {
|
|
15414
|
+
return null;
|
|
15415
|
+
}
|
|
15416
|
+
return this._scissorStack[depth];
|
|
15417
|
+
}
|
|
15369
15418
|
/**
|
|
15370
15419
|
* Restore state from the stack.
|
|
15371
15420
|
* Color, tint, transform, and scissor are restored in place.
|
|
@@ -15466,6 +15515,8 @@ var Renderer = class {
|
|
|
15466
15515
|
this.maskLevel = 0;
|
|
15467
15516
|
this.projectionMatrix = new Matrix3d();
|
|
15468
15517
|
this.uvOffset = 0;
|
|
15518
|
+
this.currentNormalMap = null;
|
|
15519
|
+
this.activeLightCount = 0;
|
|
15469
15520
|
}
|
|
15470
15521
|
/**
|
|
15471
15522
|
* @type {string}
|
|
@@ -15686,6 +15737,97 @@ var Renderer = class {
|
|
|
15686
15737
|
setBlendMode(mode = "normal") {
|
|
15687
15738
|
this.currentBlendMode = mode;
|
|
15688
15739
|
}
|
|
15740
|
+
/**
|
|
15741
|
+
* Upload the active scene lights to the lit sprite pipeline.
|
|
15742
|
+
*
|
|
15743
|
+
* Called once per camera per frame by `Camera2d.draw()` (after the
|
|
15744
|
+
* FBO is bound, before the world tree walk fires `Sprite.draw` for
|
|
15745
|
+
* any normal-mapped sprite). The WebGL renderer overrides this to
|
|
15746
|
+
* pack the lights into the lit shader's uniform buffers; the Canvas
|
|
15747
|
+
* renderer cannot do per-pixel normal-map lighting and silently
|
|
15748
|
+
* ignores the call. The first time a non-empty light list is passed
|
|
15749
|
+
* in Canvas mode, a one-shot console warning is emitted.
|
|
15750
|
+
*
|
|
15751
|
+
* Stage stays renderer-agnostic by passing the raw scene data —
|
|
15752
|
+
* lights iterable and ambient color — and letting the renderer
|
|
15753
|
+
* decide how to encode them.
|
|
15754
|
+
* @param {Iterable<object>} [lights] - active `Light2d` instances; falsy/empty no-ops
|
|
15755
|
+
* @param {object} [ambient] - ambient lighting color (0..255 RGB)
|
|
15756
|
+
* @param {number} [translateX=0] - world-to-screen X translate (matches `Camera2d.draw()`)
|
|
15757
|
+
* @param {number} [translateY=0] - world-to-screen Y translate
|
|
15758
|
+
*/
|
|
15759
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
15760
|
+
setLightUniforms(lights, ambient, translateX, translateY) {
|
|
15761
|
+
if (this._litPipelineWarned || !lights) {
|
|
15762
|
+
return;
|
|
15763
|
+
}
|
|
15764
|
+
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;
|
|
15765
|
+
if (hasAny) {
|
|
15766
|
+
this._litPipelineWarned = true;
|
|
15767
|
+
console.warn(
|
|
15768
|
+
"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."
|
|
15769
|
+
);
|
|
15770
|
+
}
|
|
15771
|
+
}
|
|
15772
|
+
/**
|
|
15773
|
+
* Render a `Light2d` instance.
|
|
15774
|
+
*
|
|
15775
|
+
* Each renderer implements its own strategy: the WebGL renderer
|
|
15776
|
+
* draws lights as quads through a shared procedural radial-falloff
|
|
15777
|
+
* fragment shader (no per-light texture, color and intensity
|
|
15778
|
+
* encoded in the per-vertex tint so consecutive draws batch); the
|
|
15779
|
+
* Canvas renderer caches a small `Gradient` config object per
|
|
15780
|
+
* light in a `WeakMap` (rebuilt only when the light's radii /
|
|
15781
|
+
* color / intensity change), rasterizes it with `Gradient.toCanvas()`
|
|
15782
|
+
* into a single shared `CanvasRenderTarget`, and composites the
|
|
15783
|
+
* result via `drawImage`. The base implementation is a no-op so
|
|
15784
|
+
* renderers without a lighting path can be polymorphically
|
|
15785
|
+
* substituted.
|
|
15786
|
+
*
|
|
15787
|
+
* Light2d itself is renderer-agnostic — it just calls
|
|
15788
|
+
* `renderer.drawLight(this)` and relies on the renderer to pick
|
|
15789
|
+
* the right machinery.
|
|
15790
|
+
* @param {object} light - the Light2d instance to render
|
|
15791
|
+
* @see Light2d
|
|
15792
|
+
*/
|
|
15793
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
15794
|
+
drawLight(light) {
|
|
15795
|
+
}
|
|
15796
|
+
/**
|
|
15797
|
+
* Draw a TMX tile layer. Default behavior:
|
|
15798
|
+
* - if `layer.canvasRenderer` is set (preRender bake), blit the cached
|
|
15799
|
+
* offscreen canvas in a single `drawImage` call;
|
|
15800
|
+
* - otherwise delegate to the layer's TMX orientation renderer for
|
|
15801
|
+
* the per-tile loop.
|
|
15802
|
+
*
|
|
15803
|
+
* `WebGLRenderer` overrides this to add the procedural shader fast
|
|
15804
|
+
* path on top (when `layer.renderMode === "shader"`) and fall through
|
|
15805
|
+
* to this base behavior for all other layers.
|
|
15806
|
+
* @param {object} layer - the TMXLayer to draw
|
|
15807
|
+
* @param {Rect} rect - the visible region in world coords
|
|
15808
|
+
*/
|
|
15809
|
+
drawTileLayer(layer, rect) {
|
|
15810
|
+
if (layer.canvasRenderer) {
|
|
15811
|
+
const width = Math.min(rect.width, layer.width - rect.pos.x);
|
|
15812
|
+
const height = Math.min(rect.height, layer.height - rect.pos.y);
|
|
15813
|
+
if (width <= 0 || height <= 0) {
|
|
15814
|
+
return;
|
|
15815
|
+
}
|
|
15816
|
+
this.drawImage(
|
|
15817
|
+
layer.canvasRenderer.getCanvas(),
|
|
15818
|
+
rect.pos.x,
|
|
15819
|
+
rect.pos.y,
|
|
15820
|
+
width,
|
|
15821
|
+
height,
|
|
15822
|
+
rect.pos.x,
|
|
15823
|
+
rect.pos.y,
|
|
15824
|
+
width,
|
|
15825
|
+
height
|
|
15826
|
+
);
|
|
15827
|
+
return;
|
|
15828
|
+
}
|
|
15829
|
+
layer.getRenderer().drawTileLayer(this, layer, rect);
|
|
15830
|
+
}
|
|
15689
15831
|
/**
|
|
15690
15832
|
* Set the current fill & stroke style color.
|
|
15691
15833
|
* By default, or upon reset, the value is set to #000000.
|
|
@@ -16206,7 +16348,7 @@ var TextureAtlas = class {
|
|
|
16206
16348
|
/**
|
|
16207
16349
|
* @param {object|object[]} atlases - atlas information. See {@link loader.getJSON}
|
|
16208
16350
|
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|CompressedImage|string|OffscreenCanvas[]|HTMLImageElement[]|HTMLCanvasElement[]|string[]} [src=atlas.meta.image] - Image source
|
|
16209
|
-
* @param {boolean} [cache
|
|
16351
|
+
* @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
16352
|
* @example
|
|
16211
16353
|
* // create a texture atlas from a JSON Object
|
|
16212
16354
|
* game.texture = new me.TextureAtlas(
|
|
@@ -16228,10 +16370,22 @@ var TextureAtlas = class {
|
|
|
16228
16370
|
* anchorPoint : new me.Vector2d(0.5, 0.5)
|
|
16229
16371
|
* },
|
|
16230
16372
|
* me.loader.getImage("spritesheet")
|
|
16373
|
+
* );
|
|
16374
|
+
*
|
|
16375
|
+
* // SpriteIlluminator workflow: pair the color atlas with its normal map
|
|
16376
|
+
* game.texture = new me.TextureAtlas(
|
|
16377
|
+
* me.loader.getJSON("scene"),
|
|
16378
|
+
* me.loader.getImage("scene"),
|
|
16379
|
+
* { normalMap: me.loader.getImage("scene_n") }
|
|
16380
|
+
* );
|
|
16231
16381
|
*/
|
|
16232
|
-
constructor(atlases, src,
|
|
16382
|
+
constructor(atlases, src, options) {
|
|
16383
|
+
const opts = typeof options === "boolean" ? { cache: options } : options || {};
|
|
16384
|
+
const cache2 = opts.cache;
|
|
16385
|
+
const normalMap = opts.normalMap;
|
|
16233
16386
|
this.format = null;
|
|
16234
16387
|
this.sources = /* @__PURE__ */ new Map();
|
|
16388
|
+
this.normalSources = /* @__PURE__ */ new Map();
|
|
16235
16389
|
this.atlases = /* @__PURE__ */ new Map();
|
|
16236
16390
|
this.activeAtlas = void 0;
|
|
16237
16391
|
this._uvCache = { sx: -1, sy: -1, sw: -1, sh: -1, uvs: null };
|
|
@@ -16321,6 +16475,31 @@ var TextureAtlas = class {
|
|
|
16321
16475
|
game.renderer.cache.set(source, this);
|
|
16322
16476
|
});
|
|
16323
16477
|
}
|
|
16478
|
+
if (typeof normalMap !== "undefined" && normalMap !== null) {
|
|
16479
|
+
let resolved;
|
|
16480
|
+
if (typeof normalMap === "string") {
|
|
16481
|
+
resolved = getImage(normalMap);
|
|
16482
|
+
if (!resolved) {
|
|
16483
|
+
throw new Error(
|
|
16484
|
+
"TextureAtlas: normal map image '" + normalMap + "' not found"
|
|
16485
|
+
);
|
|
16486
|
+
}
|
|
16487
|
+
} else if (typeof normalMap === "object" && typeof normalMap.width === "number" && typeof normalMap.height === "number") {
|
|
16488
|
+
if (typeof normalMap.videoWidth === "number") {
|
|
16489
|
+
throw new TypeError(
|
|
16490
|
+
"TextureAtlas: options.normalMap does not support HTMLVideoElement (the lit pipeline caches the texture per image reference and would freeze on frame 0)"
|
|
16491
|
+
);
|
|
16492
|
+
}
|
|
16493
|
+
resolved = normalMap;
|
|
16494
|
+
} else {
|
|
16495
|
+
throw new TypeError(
|
|
16496
|
+
"TextureAtlas: options.normalMap must be an image-like, a loader key string, or null/undefined; got " + typeof normalMap
|
|
16497
|
+
);
|
|
16498
|
+
}
|
|
16499
|
+
this.sources.forEach((_source, key) => {
|
|
16500
|
+
this.normalSources.set(key, resolved);
|
|
16501
|
+
});
|
|
16502
|
+
}
|
|
16324
16503
|
}
|
|
16325
16504
|
/**
|
|
16326
16505
|
* return the default or specified atlas dictionnary
|
|
@@ -16353,6 +16532,22 @@ var TextureAtlas = class {
|
|
|
16353
16532
|
return this.sources.get(this.activeAtlas);
|
|
16354
16533
|
}
|
|
16355
16534
|
}
|
|
16535
|
+
/**
|
|
16536
|
+
* Return the paired normal-map texture for the given region, or `null`
|
|
16537
|
+
* if no normal map was provided to this atlas. The normal map shares
|
|
16538
|
+
* the same UV layout as the color texture returned by {@link TextureAtlas#getTexture}.
|
|
16539
|
+
* @param {object} [region] - region name in case of multipack textures
|
|
16540
|
+
* @returns {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap|null}
|
|
16541
|
+
*/
|
|
16542
|
+
getNormalTexture(region) {
|
|
16543
|
+
if (this.normalSources.size === 0) {
|
|
16544
|
+
return null;
|
|
16545
|
+
}
|
|
16546
|
+
if (typeof region === "object" && typeof region.texture === "string") {
|
|
16547
|
+
return this.normalSources.get(region.texture) ?? null;
|
|
16548
|
+
}
|
|
16549
|
+
return this.normalSources.get(this.activeAtlas) ?? null;
|
|
16550
|
+
}
|
|
16356
16551
|
/**
|
|
16357
16552
|
* add a region to the atlas
|
|
16358
16553
|
* @param {string} name - region mame
|
|
@@ -16731,14 +16926,6 @@ var TextureCache = class {
|
|
|
16731
16926
|
* cache the textureAltas for the given image
|
|
16732
16927
|
*/
|
|
16733
16928
|
set(image, textureAtlas) {
|
|
16734
|
-
const width = image.width || image.videoWidth;
|
|
16735
|
-
const height = image.height || image.videoHeight;
|
|
16736
|
-
if (this.renderer.WebGLVersion === 1 && (!isPowerOfTwo(width) || !isPowerOfTwo(height))) {
|
|
16737
|
-
const src = typeof image.src !== "undefined" ? image.src : image;
|
|
16738
|
-
console.warn(
|
|
16739
|
-
"[Texture] " + src + " is not a POT texture (" + width + "x" + height + ")"
|
|
16740
|
-
);
|
|
16741
|
-
}
|
|
16742
16929
|
return this.cache.put(image, textureAtlas);
|
|
16743
16930
|
}
|
|
16744
16931
|
/**
|
|
@@ -16851,6 +17038,7 @@ var CanvasRenderer = class extends Renderer {
|
|
|
16851
17038
|
reset() {
|
|
16852
17039
|
super.reset();
|
|
16853
17040
|
this.clearColor(this.currentColor, this.settings.transparent !== true);
|
|
17041
|
+
this._lightCache = void 0;
|
|
16854
17042
|
}
|
|
16855
17043
|
/**
|
|
16856
17044
|
* Reset the canvas transform to identity
|
|
@@ -17044,6 +17232,59 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17044
17232
|
}
|
|
17045
17233
|
context.drawImage(source, sx, sy, sw, sh, dx, dy, dw, dh);
|
|
17046
17234
|
}
|
|
17235
|
+
/**
|
|
17236
|
+
* @inheritdoc
|
|
17237
|
+
*
|
|
17238
|
+
* Renders the light by drawing a cached radial `Gradient` via
|
|
17239
|
+
* `Gradient.toCanvas()`. The Gradient instance is cached per-Light2d
|
|
17240
|
+
* in a `WeakMap` and rebuilt only when the light's radii / color /
|
|
17241
|
+
* intensity change. `toCanvas` itself shares a single
|
|
17242
|
+
* `CanvasRenderTarget` across all gradients in the engine, so memory
|
|
17243
|
+
* stays at O(1) regardless of how many lights are active.
|
|
17244
|
+
*
|
|
17245
|
+
* The cached Gradient is always circular (outer radius =
|
|
17246
|
+
* `max(radiusX, radiusY)`) — `drawImage`'s non-uniform stretch
|
|
17247
|
+
* produces the elliptical falloff for non-square lights, matching
|
|
17248
|
+
* the procedural shader's behavior on WebGL.
|
|
17249
|
+
* @param {object} light - the Light2d instance to render
|
|
17250
|
+
*/
|
|
17251
|
+
drawLight(light) {
|
|
17252
|
+
if (this._lightCache === void 0) {
|
|
17253
|
+
this._lightCache = /* @__PURE__ */ new WeakMap();
|
|
17254
|
+
}
|
|
17255
|
+
let entry = this._lightCache.get(light);
|
|
17256
|
+
const c = light.color;
|
|
17257
|
+
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) {
|
|
17258
|
+
const r = Math.max(light.radiusX, light.radiusY);
|
|
17259
|
+
const gradient = this.createRadialGradient(r, r, 0, r, r, r);
|
|
17260
|
+
gradient.addColorStop(0, c.toRGBA(light.intensity));
|
|
17261
|
+
gradient.addColorStop(1, c.toRGBA(0));
|
|
17262
|
+
entry = {
|
|
17263
|
+
gradient,
|
|
17264
|
+
radius: r,
|
|
17265
|
+
radiusX: light.radiusX,
|
|
17266
|
+
radiusY: light.radiusY,
|
|
17267
|
+
r: c.r,
|
|
17268
|
+
g: c.g,
|
|
17269
|
+
b: c.b,
|
|
17270
|
+
intensity: light.intensity
|
|
17271
|
+
};
|
|
17272
|
+
this._lightCache.set(light, entry);
|
|
17273
|
+
}
|
|
17274
|
+
const r2 = entry.radius * 2;
|
|
17275
|
+
const canvas = entry.gradient.toCanvas(this, 0, 0, r2, r2);
|
|
17276
|
+
this.drawImage(
|
|
17277
|
+
canvas,
|
|
17278
|
+
0,
|
|
17279
|
+
0,
|
|
17280
|
+
r2,
|
|
17281
|
+
r2,
|
|
17282
|
+
light.pos.x,
|
|
17283
|
+
light.pos.y,
|
|
17284
|
+
light.width,
|
|
17285
|
+
light.height
|
|
17286
|
+
);
|
|
17287
|
+
}
|
|
17047
17288
|
/**
|
|
17048
17289
|
* Draw a pattern within the given rectangle.
|
|
17049
17290
|
* @param {CanvasPattern} pattern - Pattern object
|
|
@@ -17778,14 +18019,15 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17778
18019
|
if (typeof mask !== "undefined") {
|
|
17779
18020
|
context.beginPath();
|
|
17780
18021
|
}
|
|
18022
|
+
this._maskInvertOuterAdded = false;
|
|
17781
18023
|
}
|
|
17782
18024
|
if (typeof mask !== "undefined") {
|
|
17783
18025
|
switch (mask.type) {
|
|
17784
18026
|
// RoundRect
|
|
17785
18027
|
case "RoundRect":
|
|
17786
18028
|
context.roundRect(
|
|
17787
|
-
mask.top,
|
|
17788
18029
|
mask.left,
|
|
18030
|
+
mask.top,
|
|
17789
18031
|
mask.width,
|
|
17790
18032
|
mask.height,
|
|
17791
18033
|
mask.radius
|
|
@@ -17794,7 +18036,7 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17794
18036
|
// Rect or Bounds
|
|
17795
18037
|
case "Rectangle":
|
|
17796
18038
|
case "Bounds":
|
|
17797
|
-
context.rect(mask.
|
|
18039
|
+
context.rect(mask.left, mask.top, mask.width, mask.height);
|
|
17798
18040
|
break;
|
|
17799
18041
|
// Polygon or Line
|
|
17800
18042
|
case "Polygon":
|
|
@@ -17837,7 +18079,10 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17837
18079
|
}
|
|
17838
18080
|
this.maskLevel++;
|
|
17839
18081
|
if (invert === true) {
|
|
17840
|
-
|
|
18082
|
+
if (this._maskInvertOuterAdded !== true) {
|
|
18083
|
+
context.rect(0, 0, this.getCanvas().width, this.getCanvas().height);
|
|
18084
|
+
this._maskInvertOuterAdded = true;
|
|
18085
|
+
}
|
|
17841
18086
|
context.clip("evenodd");
|
|
17842
18087
|
} else {
|
|
17843
18088
|
context.clip();
|
|
@@ -17856,6 +18101,30 @@ var CanvasRenderer = class extends Renderer {
|
|
|
17856
18101
|
};
|
|
17857
18102
|
|
|
17858
18103
|
// src/level/tiled/TMXTile.js
|
|
18104
|
+
var FLIP_H_BIT = 1 << 0;
|
|
18105
|
+
var FLIP_V_BIT = 1 << 1;
|
|
18106
|
+
var FLIP_AD_BIT = 1 << 2;
|
|
18107
|
+
function buildFlipTransform(transform, flipMask, width, height) {
|
|
18108
|
+
const halfW = width / 2;
|
|
18109
|
+
const halfH = height / 2;
|
|
18110
|
+
const flippedH = (flipMask & FLIP_H_BIT) !== 0;
|
|
18111
|
+
const flippedV = (flipMask & FLIP_V_BIT) !== 0;
|
|
18112
|
+
const flippedAD = (flipMask & FLIP_AD_BIT) !== 0;
|
|
18113
|
+
transform.identity();
|
|
18114
|
+
transform.translate(halfW, halfH);
|
|
18115
|
+
if (flippedAD) {
|
|
18116
|
+
transform.rotate(degToRad(-90));
|
|
18117
|
+
transform.scale(-1, 1);
|
|
18118
|
+
}
|
|
18119
|
+
if (flippedH) {
|
|
18120
|
+
transform.scale(flippedAD ? 1 : -1, flippedAD ? -1 : 1);
|
|
18121
|
+
}
|
|
18122
|
+
if (flippedV) {
|
|
18123
|
+
transform.scale(flippedAD ? -1 : 1, flippedAD ? 1 : -1);
|
|
18124
|
+
}
|
|
18125
|
+
transform.translate(-halfW, -halfH);
|
|
18126
|
+
return transform;
|
|
18127
|
+
}
|
|
17859
18128
|
var Tile = class extends Bounds {
|
|
17860
18129
|
/**
|
|
17861
18130
|
* @param {number} x - x index of the Tile in the map
|
|
@@ -18406,12 +18675,17 @@ function applyTMXProperties(obj, data2) {
|
|
|
18406
18675
|
}
|
|
18407
18676
|
|
|
18408
18677
|
// src/level/tiled/TMXLayer.js
|
|
18409
|
-
|
|
18410
|
-
|
|
18411
|
-
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
|
|
18678
|
+
var FLIP_H_BIT2 = 1 << 0;
|
|
18679
|
+
var FLIP_V_BIT2 = 1 << 1;
|
|
18680
|
+
var FLIP_AD_BIT2 = 1 << 2;
|
|
18681
|
+
function flipMaskFromGid(gid) {
|
|
18682
|
+
return (gid & TMX_FLIP_H ? FLIP_H_BIT2 : 0) | (gid & TMX_FLIP_V ? FLIP_V_BIT2 : 0) | (gid & TMX_FLIP_AD ? FLIP_AD_BIT2 : 0);
|
|
18683
|
+
}
|
|
18684
|
+
function flipMaskFromTile(tile) {
|
|
18685
|
+
return (tile.flippedX ? FLIP_H_BIT2 : 0) | (tile.flippedY ? FLIP_V_BIT2 : 0) | (tile.flippedAD ? FLIP_AD_BIT2 : 0);
|
|
18686
|
+
}
|
|
18687
|
+
function gidWithFlips(gid, flipMask) {
|
|
18688
|
+
return gid | (flipMask & FLIP_H_BIT2 ? TMX_FLIP_H : 0) | (flipMask & FLIP_V_BIT2 ? TMX_FLIP_V : 0) | (flipMask & FLIP_AD_BIT2 ? TMX_FLIP_AD : 0);
|
|
18415
18689
|
}
|
|
18416
18690
|
function setLayerData(layer, bounds, data2) {
|
|
18417
18691
|
let idx = 0;
|
|
@@ -18424,18 +18698,30 @@ function setLayerData(layer, bounds, data2) {
|
|
|
18424
18698
|
width = bounds.cols;
|
|
18425
18699
|
height = bounds.rows;
|
|
18426
18700
|
}
|
|
18701
|
+
const cols = layer.cols;
|
|
18702
|
+
const layerData = layer.layerData;
|
|
18703
|
+
const offsetX = bounds.x;
|
|
18704
|
+
const offsetY = bounds.y;
|
|
18705
|
+
let overflowedGid = 0;
|
|
18427
18706
|
for (let y = 0; y < height; y++) {
|
|
18428
18707
|
for (let x = 0; x < width; x++) {
|
|
18429
|
-
const
|
|
18430
|
-
if (
|
|
18431
|
-
|
|
18432
|
-
|
|
18433
|
-
|
|
18434
|
-
|
|
18435
|
-
|
|
18708
|
+
const rawGid = data2[idx++];
|
|
18709
|
+
if (rawGid !== 0) {
|
|
18710
|
+
const flatIdx = ((y + offsetY) * cols + (x + offsetX)) * 2;
|
|
18711
|
+
const cleanGid = rawGid & TMX_CLEAR_BIT_MASK;
|
|
18712
|
+
if (cleanGid > 65535 && overflowedGid === 0) {
|
|
18713
|
+
overflowedGid = cleanGid;
|
|
18714
|
+
}
|
|
18715
|
+
layerData[flatIdx] = cleanGid;
|
|
18716
|
+
layerData[flatIdx + 1] = flipMaskFromGid(rawGid);
|
|
18436
18717
|
}
|
|
18437
18718
|
}
|
|
18438
18719
|
}
|
|
18720
|
+
if (overflowedGid !== 0) {
|
|
18721
|
+
console.warn(
|
|
18722
|
+
"melonJS: TMX layer contains GID " + overflowedGid + " which exceeds the 16-bit cell capacity (max 65535). Tiles will be truncated and render incorrectly."
|
|
18723
|
+
);
|
|
18724
|
+
}
|
|
18439
18725
|
}
|
|
18440
18726
|
var TMXLayer = class extends Renderable {
|
|
18441
18727
|
/**
|
|
@@ -18495,7 +18781,10 @@ var TMXLayer = class extends Renderable {
|
|
|
18495
18781
|
}
|
|
18496
18782
|
applyTMXProperties(this, data2);
|
|
18497
18783
|
this.setRenderer(map.getRenderer());
|
|
18498
|
-
this.layerData =
|
|
18784
|
+
this.layerData = new Uint16Array(this.cols * this.rows * 2);
|
|
18785
|
+
this.cachedTile = null;
|
|
18786
|
+
this.dataVersion = 0;
|
|
18787
|
+
this.renderMode = "auto";
|
|
18499
18788
|
if (map.infinite === 0) {
|
|
18500
18789
|
setLayerData(
|
|
18501
18790
|
this,
|
|
@@ -18525,12 +18814,8 @@ var TMXLayer = class extends Renderable {
|
|
|
18525
18814
|
}
|
|
18526
18815
|
}
|
|
18527
18816
|
this.isAnimated = this.animatedTilesets.length > 0;
|
|
18528
|
-
|
|
18529
|
-
|
|
18530
|
-
} else {
|
|
18531
|
-
this.preRender = false;
|
|
18532
|
-
}
|
|
18533
|
-
if (this.preRender === true && !this.canvasRenderer) {
|
|
18817
|
+
this._resolveRenderMode();
|
|
18818
|
+
if (this.renderMode === "prerender" && !this.canvasRenderer) {
|
|
18534
18819
|
this.canvasRenderer = new CanvasRenderer({
|
|
18535
18820
|
canvas: createCanvas(this.width, this.height),
|
|
18536
18821
|
width: this.width,
|
|
@@ -18539,8 +18824,109 @@ var TMXLayer = class extends Renderable {
|
|
|
18539
18824
|
});
|
|
18540
18825
|
this.getRenderer().drawTileLayer(this.canvasRenderer, this, this);
|
|
18541
18826
|
}
|
|
18827
|
+
this.preRender = this.renderMode === "prerender";
|
|
18542
18828
|
this.isDirty = true;
|
|
18543
18829
|
}
|
|
18830
|
+
/**
|
|
18831
|
+
* Resolve `this.renderMode` to one of "shader" / "prerender" / "perTile"
|
|
18832
|
+
* based on eligibility checks and user/world hints. Emits a single
|
|
18833
|
+
* `console.warn` at activation when a forced mode is ineligible, or
|
|
18834
|
+
* when an auto-eligible mode falls back due to a layer feature the GPU
|
|
18835
|
+
* path doesn't support (orientation, collection-of-image tileset, etc.).
|
|
18836
|
+
* @ignore
|
|
18837
|
+
*/
|
|
18838
|
+
_resolveRenderMode() {
|
|
18839
|
+
const root = this.ancestor?.getRootAncestor?.();
|
|
18840
|
+
const renderer2 = this.parentApp?.renderer;
|
|
18841
|
+
const gpuAllowed = root?.gpuTilemap !== false;
|
|
18842
|
+
const preRenderHint = typeof this.preRender === "boolean" ? this.preRender : root?.preRender;
|
|
18843
|
+
const elig = this._checkShaderEligibility(renderer2, gpuAllowed);
|
|
18844
|
+
const requested = this.renderMode;
|
|
18845
|
+
if (requested === "shader") {
|
|
18846
|
+
if (elig.ok) {
|
|
18847
|
+
return;
|
|
18848
|
+
}
|
|
18849
|
+
console.warn(
|
|
18850
|
+
`melonJS: layer "${this.name}" forced renderMode "shader" not available (${elig.reason}) \u2014 falling back to perTile`
|
|
18851
|
+
);
|
|
18852
|
+
this.renderMode = "perTile";
|
|
18853
|
+
return;
|
|
18854
|
+
}
|
|
18855
|
+
if (requested === "prerender") {
|
|
18856
|
+
if (this.isAnimated) {
|
|
18857
|
+
console.warn(
|
|
18858
|
+
`melonJS: layer "${this.name}" forced renderMode "prerender" disabled (layer has animated tiles) \u2014 falling back to perTile`
|
|
18859
|
+
);
|
|
18860
|
+
this.renderMode = "perTile";
|
|
18861
|
+
}
|
|
18862
|
+
return;
|
|
18863
|
+
}
|
|
18864
|
+
if (requested === "perTile") {
|
|
18865
|
+
return;
|
|
18866
|
+
}
|
|
18867
|
+
if (elig.ok) {
|
|
18868
|
+
this.renderMode = "shader";
|
|
18869
|
+
return;
|
|
18870
|
+
}
|
|
18871
|
+
if (gpuAllowed && elig.reason !== "no-webgl2-renderer") {
|
|
18872
|
+
console.warn(
|
|
18873
|
+
`melonJS: layer "${this.name}" using legacy tile renderer (${elig.reason})`
|
|
18874
|
+
);
|
|
18875
|
+
}
|
|
18876
|
+
if (preRenderHint && !this.isAnimated) {
|
|
18877
|
+
this.renderMode = "prerender";
|
|
18878
|
+
return;
|
|
18879
|
+
}
|
|
18880
|
+
this.renderMode = "perTile";
|
|
18881
|
+
}
|
|
18882
|
+
/**
|
|
18883
|
+
* Check whether this layer is eligible for the WebGL2 shader path.
|
|
18884
|
+
* @param {object} renderer
|
|
18885
|
+
* @param {boolean} gpuAllowed - whether `gpuTilemap` is enabled at the world level
|
|
18886
|
+
* @returns {{ok: boolean, reason?: string}}
|
|
18887
|
+
* @ignore
|
|
18888
|
+
*/
|
|
18889
|
+
_checkShaderEligibility(renderer2, gpuAllowed) {
|
|
18890
|
+
if (!gpuAllowed) {
|
|
18891
|
+
return { ok: false, reason: "gpuTilemap disabled" };
|
|
18892
|
+
}
|
|
18893
|
+
if (!renderer2 || renderer2.WebGLVersion !== 2) {
|
|
18894
|
+
return { ok: false, reason: "no-webgl2-renderer" };
|
|
18895
|
+
}
|
|
18896
|
+
if (this.orientation !== "orthogonal") {
|
|
18897
|
+
return {
|
|
18898
|
+
ok: false,
|
|
18899
|
+
reason: `no gpu renderer supported yet for "${this.orientation}" orientation`
|
|
18900
|
+
};
|
|
18901
|
+
}
|
|
18902
|
+
if (!this.tilesets || this.tilesets.tilesets.length === 0) {
|
|
18903
|
+
return { ok: false, reason: "no tilesets" };
|
|
18904
|
+
}
|
|
18905
|
+
const MAX_OVERFLOW_CELLS = 4;
|
|
18906
|
+
for (const ts of this.tilesets.tilesets) {
|
|
18907
|
+
if (ts.isCollection) {
|
|
18908
|
+
return { ok: false, reason: "collection-of-image tileset" };
|
|
18909
|
+
}
|
|
18910
|
+
if (ts.tilerendersize !== "tile") {
|
|
18911
|
+
return {
|
|
18912
|
+
ok: false,
|
|
18913
|
+
reason: `tilerendersize "${ts.tilerendersize}" not supported`
|
|
18914
|
+
};
|
|
18915
|
+
}
|
|
18916
|
+
if (ts.tileoffset.x !== 0 || ts.tileoffset.y !== 0) {
|
|
18917
|
+
return { ok: false, reason: "non-zero tileoffset" };
|
|
18918
|
+
}
|
|
18919
|
+
const overflowX = Math.ceil(ts.tilewidth / this.tilewidth) - 1;
|
|
18920
|
+
const overflowY = Math.ceil(ts.tileheight / this.tileheight) - 1;
|
|
18921
|
+
if (overflowX > MAX_OVERFLOW_CELLS || overflowY > MAX_OVERFLOW_CELLS) {
|
|
18922
|
+
return {
|
|
18923
|
+
ok: false,
|
|
18924
|
+
reason: `tile overflow exceeds shader limit (${MAX_OVERFLOW_CELLS} cells)`
|
|
18925
|
+
};
|
|
18926
|
+
}
|
|
18927
|
+
}
|
|
18928
|
+
return { ok: true };
|
|
18929
|
+
}
|
|
18544
18930
|
// called when the layer is removed from the game world or a container
|
|
18545
18931
|
onDeactivateEvent() {
|
|
18546
18932
|
this.animatedTilesets = void 0;
|
|
@@ -18601,12 +18987,29 @@ var TMXLayer = class extends Renderable {
|
|
|
18601
18987
|
/**
|
|
18602
18988
|
* assign the given Tile object to the specified position
|
|
18603
18989
|
* @param {Tile} tile - the tile object to be assigned
|
|
18604
|
-
* @param {number} x - x coordinate (in
|
|
18605
|
-
* @param {number} y - y coordinate (in
|
|
18990
|
+
* @param {number} x - x coordinate (in tile/column coordinates)
|
|
18991
|
+
* @param {number} y - y coordinate (in tile/row coordinates)
|
|
18606
18992
|
* @returns {Tile} the tile object
|
|
18607
18993
|
*/
|
|
18608
18994
|
setTile(tile, x, y) {
|
|
18609
|
-
this.
|
|
18995
|
+
if (x < 0 || x >= this.cols || y < 0 || y >= this.rows) {
|
|
18996
|
+
return tile;
|
|
18997
|
+
}
|
|
18998
|
+
const slot = y * this.cols + x;
|
|
18999
|
+
const idx = slot * 2;
|
|
19000
|
+
const cleanGid = tile.tileId & TMX_CLEAR_BIT_MASK;
|
|
19001
|
+
if (cleanGid > 65535 && !this._truncationWarned) {
|
|
19002
|
+
this._truncationWarned = true;
|
|
19003
|
+
console.warn(
|
|
19004
|
+
"melonJS: setTile received GID " + cleanGid + " which exceeds the 16-bit cell capacity (max 65535). Tile will be truncated and render incorrectly."
|
|
19005
|
+
);
|
|
19006
|
+
}
|
|
19007
|
+
this.layerData[idx] = cleanGid;
|
|
19008
|
+
this.layerData[idx + 1] = flipMaskFromTile(tile);
|
|
19009
|
+
if (this.cachedTile !== null) {
|
|
19010
|
+
this.cachedTile[slot] = tile;
|
|
19011
|
+
}
|
|
19012
|
+
this.dataVersion++;
|
|
18610
19013
|
this.isDirty = true;
|
|
18611
19014
|
return tile;
|
|
18612
19015
|
}
|
|
@@ -18636,12 +19039,27 @@ var TMXLayer = class extends Renderable {
|
|
|
18636
19039
|
cellAt(x, y, boundsCheck) {
|
|
18637
19040
|
const _x = ~~x;
|
|
18638
19041
|
const _y = ~~y;
|
|
18639
|
-
|
|
18640
|
-
if (boundsCheck === false || _x >= 0 && _x < renderer2.cols && _y >= 0 && _y < renderer2.rows) {
|
|
18641
|
-
return this.layerData[_x][_y];
|
|
18642
|
-
} else {
|
|
19042
|
+
if (boundsCheck !== false && (_x < 0 || _x >= this.cols || _y < 0 || _y >= this.rows)) {
|
|
18643
19043
|
return null;
|
|
18644
19044
|
}
|
|
19045
|
+
const slot = _y * this.cols + _x;
|
|
19046
|
+
const idx = slot * 2;
|
|
19047
|
+
const gid = this.layerData[idx];
|
|
19048
|
+
if (!gid) {
|
|
19049
|
+
return null;
|
|
19050
|
+
}
|
|
19051
|
+
if (this.cachedTile === null) {
|
|
19052
|
+
this.cachedTile = new Array(this.cols * this.rows).fill(null);
|
|
19053
|
+
} else {
|
|
19054
|
+
const cached = this.cachedTile[slot];
|
|
19055
|
+
if (cached !== null) {
|
|
19056
|
+
return cached;
|
|
19057
|
+
}
|
|
19058
|
+
}
|
|
19059
|
+
const flipMask = this.layerData[idx + 1];
|
|
19060
|
+
const tile = this.getTileById(gidWithFlips(gid, flipMask), _x, _y);
|
|
19061
|
+
this.cachedTile[slot] = tile;
|
|
19062
|
+
return tile;
|
|
18645
19063
|
}
|
|
18646
19064
|
/**
|
|
18647
19065
|
* clear the tile at the specified position
|
|
@@ -18654,7 +19072,16 @@ var TMXLayer = class extends Renderable {
|
|
|
18654
19072
|
* });
|
|
18655
19073
|
*/
|
|
18656
19074
|
clearTile(x, y) {
|
|
18657
|
-
this.
|
|
19075
|
+
if (x < 0 || x >= this.cols || y < 0 || y >= this.rows) {
|
|
19076
|
+
return;
|
|
19077
|
+
}
|
|
19078
|
+
const slot = y * this.cols + x;
|
|
19079
|
+
const idx = slot * 2;
|
|
19080
|
+
this.layerData[idx] = 0;
|
|
19081
|
+
this.layerData[idx + 1] = 0;
|
|
19082
|
+
if (this.cachedTile !== null) {
|
|
19083
|
+
this.cachedTile[slot] = null;
|
|
19084
|
+
}
|
|
18658
19085
|
if (this.preRender) {
|
|
18659
19086
|
this.canvasRenderer.clearRect(
|
|
18660
19087
|
x * this.tilewidth,
|
|
@@ -18663,6 +19090,7 @@ var TMXLayer = class extends Renderable {
|
|
|
18663
19090
|
this.tileheight
|
|
18664
19091
|
);
|
|
18665
19092
|
}
|
|
19093
|
+
this.dataVersion++;
|
|
18666
19094
|
this.isDirty = true;
|
|
18667
19095
|
}
|
|
18668
19096
|
/**
|
|
@@ -18683,27 +19111,7 @@ var TMXLayer = class extends Renderable {
|
|
|
18683
19111
|
* @ignore
|
|
18684
19112
|
*/
|
|
18685
19113
|
draw(renderer2, rect) {
|
|
18686
|
-
|
|
18687
|
-
const width = Math.min(rect.width, this.width);
|
|
18688
|
-
const height = Math.min(rect.height, this.height);
|
|
18689
|
-
renderer2.drawImage(
|
|
18690
|
-
this.canvasRenderer.getCanvas(),
|
|
18691
|
-
rect.pos.x,
|
|
18692
|
-
rect.pos.y,
|
|
18693
|
-
// sx,sy
|
|
18694
|
-
width,
|
|
18695
|
-
height,
|
|
18696
|
-
// sw,sh
|
|
18697
|
-
rect.pos.x,
|
|
18698
|
-
rect.pos.y,
|
|
18699
|
-
// dx,dy
|
|
18700
|
-
width,
|
|
18701
|
-
height
|
|
18702
|
-
// dw,dh
|
|
18703
|
-
);
|
|
18704
|
-
} else {
|
|
18705
|
-
this.getRenderer().drawTileLayer(renderer2, this, rect);
|
|
18706
|
-
}
|
|
19114
|
+
renderer2.drawTileLayer(this, rect);
|
|
18707
19115
|
}
|
|
18708
19116
|
};
|
|
18709
19117
|
|
|
@@ -19067,7 +19475,7 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
|
|
|
19067
19475
|
return ret;
|
|
19068
19476
|
}
|
|
19069
19477
|
/**
|
|
19070
|
-
* draw the tile map
|
|
19478
|
+
* draw the tile map (legacy entry point — accepts a fully-constructed Tile)
|
|
19071
19479
|
* @ignore
|
|
19072
19480
|
*/
|
|
19073
19481
|
drawTile(renderer2, x, y, tmxTile) {
|
|
@@ -19081,12 +19489,27 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
|
|
|
19081
19489
|
);
|
|
19082
19490
|
vector2dPool.release(point);
|
|
19083
19491
|
}
|
|
19492
|
+
/**
|
|
19493
|
+
* draw a tile from raw (gid, flipMask, tileset) data — used by the hot
|
|
19494
|
+
* rendering loop to bypass Tile construction
|
|
19495
|
+
* @ignore
|
|
19496
|
+
*/
|
|
19497
|
+
drawTileRaw(renderer2, x, y, gid, flipMask, tileset) {
|
|
19498
|
+
const point = this.tileToPixelCoords(x, y, vector2dPool.get());
|
|
19499
|
+
tileset.drawTileRaw(
|
|
19500
|
+
renderer2,
|
|
19501
|
+
tileset.tileoffset.x + point.x,
|
|
19502
|
+
tileset.tileoffset.y + point.y + (this.tileheight - tileset.tileheight),
|
|
19503
|
+
gid,
|
|
19504
|
+
flipMask
|
|
19505
|
+
);
|
|
19506
|
+
vector2dPool.release(point);
|
|
19507
|
+
}
|
|
19084
19508
|
/**
|
|
19085
19509
|
* draw the tile map
|
|
19086
19510
|
* @ignore
|
|
19087
19511
|
*/
|
|
19088
19512
|
drawTileLayer(renderer2, layer, rect) {
|
|
19089
|
-
let tile;
|
|
19090
19513
|
const startTile = this.pixelToTileCoords(
|
|
19091
19514
|
rect.pos.x,
|
|
19092
19515
|
rect.pos.y,
|
|
@@ -19110,6 +19533,15 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
|
|
|
19110
19533
|
}
|
|
19111
19534
|
const endX = layer.cols;
|
|
19112
19535
|
const endY = layer.rows;
|
|
19536
|
+
const layerCols = layer.cols;
|
|
19537
|
+
const data2 = layer.layerData;
|
|
19538
|
+
const tilesets = layer.tilesets;
|
|
19539
|
+
let tilesetCache = layer.tileset;
|
|
19540
|
+
if (tilesetCache === null) {
|
|
19541
|
+
vector2dPool.release(startTile);
|
|
19542
|
+
vector2dPool.release(startPos);
|
|
19543
|
+
return;
|
|
19544
|
+
}
|
|
19113
19545
|
if (this.staggerX) {
|
|
19114
19546
|
startTile.x = Math.max(0, startTile.x);
|
|
19115
19547
|
startTile.y = Math.max(0, startTile.y);
|
|
@@ -19123,9 +19555,22 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
|
|
|
19123
19555
|
rowTile.setV(startTile);
|
|
19124
19556
|
rowPos.setV(startPos);
|
|
19125
19557
|
for (; rowPos.x < rect.right && rowTile.x < endX; rowTile.x += 2) {
|
|
19126
|
-
|
|
19127
|
-
|
|
19128
|
-
|
|
19558
|
+
if (rowTile.x >= 0 && rowTile.y >= 0 && rowTile.y < layer.rows) {
|
|
19559
|
+
const idx = (rowTile.y * layerCols + rowTile.x) * 2;
|
|
19560
|
+
const gid = data2[idx];
|
|
19561
|
+
if (gid) {
|
|
19562
|
+
const flipMask = data2[idx + 1];
|
|
19563
|
+
if (!tilesetCache.contains(gid)) {
|
|
19564
|
+
tilesetCache = tilesets.getTilesetByGid(gid);
|
|
19565
|
+
}
|
|
19566
|
+
tilesetCache.drawTileRaw(
|
|
19567
|
+
renderer2,
|
|
19568
|
+
rowPos.x,
|
|
19569
|
+
rowPos.y,
|
|
19570
|
+
gid,
|
|
19571
|
+
flipMask
|
|
19572
|
+
);
|
|
19573
|
+
}
|
|
19129
19574
|
}
|
|
19130
19575
|
rowPos.x += this.tilewidth + this.sidelengthx;
|
|
19131
19576
|
}
|
|
@@ -19161,9 +19606,22 @@ var TMXHexagonalRenderer = class extends TMXRenderer {
|
|
|
19161
19606
|
rowPos.x += this.columnwidth;
|
|
19162
19607
|
}
|
|
19163
19608
|
for (; rowPos.x < rect.right && rowTile.x < endX; rowTile.x++) {
|
|
19164
|
-
|
|
19165
|
-
|
|
19166
|
-
|
|
19609
|
+
if (rowTile.x >= 0 && rowTile.y >= 0 && rowTile.y < layer.rows) {
|
|
19610
|
+
const idx = (rowTile.y * layerCols + rowTile.x) * 2;
|
|
19611
|
+
const gid = data2[idx];
|
|
19612
|
+
if (gid) {
|
|
19613
|
+
const flipMask = data2[idx + 1];
|
|
19614
|
+
if (!tilesetCache.contains(gid)) {
|
|
19615
|
+
tilesetCache = tilesets.getTilesetByGid(gid);
|
|
19616
|
+
}
|
|
19617
|
+
tilesetCache.drawTileRaw(
|
|
19618
|
+
renderer2,
|
|
19619
|
+
rowPos.x,
|
|
19620
|
+
rowPos.y,
|
|
19621
|
+
gid,
|
|
19622
|
+
flipMask
|
|
19623
|
+
);
|
|
19624
|
+
}
|
|
19167
19625
|
}
|
|
19168
19626
|
rowPos.x += this.tilewidth + this.sidelengthx;
|
|
19169
19627
|
}
|
|
@@ -19246,7 +19704,7 @@ var TMXIsometricRenderer = class extends TMXRenderer {
|
|
|
19246
19704
|
vector2dPool.release(isoPos);
|
|
19247
19705
|
}
|
|
19248
19706
|
/**
|
|
19249
|
-
* draw the tile map
|
|
19707
|
+
* draw the tile map (legacy entry point — accepts a fully-constructed Tile)
|
|
19250
19708
|
* @ignore
|
|
19251
19709
|
*/
|
|
19252
19710
|
drawTile(renderer2, x, y, tmxTile) {
|
|
@@ -19258,6 +19716,20 @@ var TMXIsometricRenderer = class extends TMXRenderer {
|
|
|
19258
19716
|
tmxTile
|
|
19259
19717
|
);
|
|
19260
19718
|
}
|
|
19719
|
+
/**
|
|
19720
|
+
* draw a tile from raw (gid, flipMask, tileset) data — used by the hot
|
|
19721
|
+
* rendering loop to bypass Tile construction
|
|
19722
|
+
* @ignore
|
|
19723
|
+
*/
|
|
19724
|
+
drawTileRaw(renderer2, x, y, gid, flipMask, tileset) {
|
|
19725
|
+
tileset.drawTileRaw(
|
|
19726
|
+
renderer2,
|
|
19727
|
+
(this.cols - 1) * tileset.tilewidth + (x - y) * tileset.tilewidth >> 1,
|
|
19728
|
+
-tileset.tilewidth + (x + y) * tileset.tileheight >> 2,
|
|
19729
|
+
gid,
|
|
19730
|
+
flipMask
|
|
19731
|
+
);
|
|
19732
|
+
}
|
|
19261
19733
|
/**
|
|
19262
19734
|
* draw the tile map
|
|
19263
19735
|
* @ignore
|
|
@@ -19300,19 +19772,41 @@ var TMXIsometricRenderer = class extends TMXRenderer {
|
|
|
19300
19772
|
}
|
|
19301
19773
|
let shifted = inUpperHalf ^ inLeftHalf;
|
|
19302
19774
|
const columnItr = vector2dPool.get().setV(rowItr);
|
|
19775
|
+
const layerCols = layer.cols;
|
|
19776
|
+
const layerRows = layer.rows;
|
|
19777
|
+
const data2 = layer.layerData;
|
|
19778
|
+
const tilesets = layer.tilesets;
|
|
19779
|
+
let tilesetCache = tileset;
|
|
19780
|
+
if (tilesetCache === null) {
|
|
19781
|
+
vector2dPool.release(columnItr);
|
|
19782
|
+
vector2dPool.release(rowItr);
|
|
19783
|
+
vector2dPool.release(tileEnd);
|
|
19784
|
+
vector2dPool.release(rectEnd);
|
|
19785
|
+
vector2dPool.release(startPos);
|
|
19786
|
+
return;
|
|
19787
|
+
}
|
|
19303
19788
|
for (let y = startPos.y * 2; y - this.tileheight * 2 < rectEnd.y * 2; y += this.tileheight) {
|
|
19304
19789
|
columnItr.setV(rowItr);
|
|
19305
19790
|
for (let x = startPos.x; x < rectEnd.x; x += this.tilewidth) {
|
|
19306
|
-
const
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
const
|
|
19310
|
-
|
|
19311
|
-
|
|
19312
|
-
|
|
19313
|
-
|
|
19314
|
-
|
|
19315
|
-
|
|
19791
|
+
const cx = columnItr.x;
|
|
19792
|
+
const cy = columnItr.y;
|
|
19793
|
+
if (cx >= 0 && cx < layerCols && cy >= 0 && cy < layerRows) {
|
|
19794
|
+
const idx = (cy * layerCols + cx) * 2;
|
|
19795
|
+
const gid = data2[idx];
|
|
19796
|
+
if (gid !== 0) {
|
|
19797
|
+
const flipMask = data2[idx + 1];
|
|
19798
|
+
if (!tilesetCache.contains(gid)) {
|
|
19799
|
+
tilesetCache = tilesets.getTilesetByGid(gid);
|
|
19800
|
+
}
|
|
19801
|
+
const offset = tilesetCache.tileoffset;
|
|
19802
|
+
tilesetCache.drawTileRaw(
|
|
19803
|
+
renderer2,
|
|
19804
|
+
offset.x + x,
|
|
19805
|
+
offset.y + y / 2 - tilesetCache.tileheight,
|
|
19806
|
+
gid,
|
|
19807
|
+
flipMask
|
|
19808
|
+
);
|
|
19809
|
+
}
|
|
19316
19810
|
}
|
|
19317
19811
|
columnItr.x++;
|
|
19318
19812
|
columnItr.y--;
|
|
@@ -19367,7 +19861,7 @@ var TMXOrthogonalRenderer = class extends TMXRenderer {
|
|
|
19367
19861
|
return ret.set(x * this.tilewidth, y * this.tileheight);
|
|
19368
19862
|
}
|
|
19369
19863
|
/**
|
|
19370
|
-
* draw the tile map
|
|
19864
|
+
* draw the tile map (legacy entry point — accepts a fully-constructed Tile)
|
|
19371
19865
|
* @ignore
|
|
19372
19866
|
*/
|
|
19373
19867
|
drawTile(renderer2, x, y, tmxTile) {
|
|
@@ -19379,6 +19873,20 @@ var TMXOrthogonalRenderer = class extends TMXRenderer {
|
|
|
19379
19873
|
tmxTile
|
|
19380
19874
|
);
|
|
19381
19875
|
}
|
|
19876
|
+
/**
|
|
19877
|
+
* draw a tile from raw (gid, flipMask, tileset) data — used by the hot
|
|
19878
|
+
* rendering loop to bypass Tile construction
|
|
19879
|
+
* @ignore
|
|
19880
|
+
*/
|
|
19881
|
+
drawTileRaw(renderer2, x, y, gid, flipMask, tileset) {
|
|
19882
|
+
tileset.drawTileRaw(
|
|
19883
|
+
renderer2,
|
|
19884
|
+
tileset.tileoffset.x + x * this.tilewidth,
|
|
19885
|
+
tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight,
|
|
19886
|
+
gid,
|
|
19887
|
+
flipMask
|
|
19888
|
+
);
|
|
19889
|
+
}
|
|
19382
19890
|
/**
|
|
19383
19891
|
* draw the tile map
|
|
19384
19892
|
* @ignore
|
|
@@ -19416,12 +19924,27 @@ var TMXOrthogonalRenderer = class extends TMXRenderer {
|
|
|
19416
19924
|
default:
|
|
19417
19925
|
break;
|
|
19418
19926
|
}
|
|
19927
|
+
const cols = layer.cols;
|
|
19928
|
+
const data2 = layer.layerData;
|
|
19929
|
+
const tilesets = layer.tilesets;
|
|
19930
|
+
let tilesetCache = layer.tileset;
|
|
19931
|
+
if (tilesetCache === null) {
|
|
19932
|
+
vector2dPool.release(start);
|
|
19933
|
+
vector2dPool.release(end);
|
|
19934
|
+
return;
|
|
19935
|
+
}
|
|
19419
19936
|
for (let y = start.y; y !== end.y; y += incY) {
|
|
19420
19937
|
for (let x = start.x; x !== end.x; x += incX) {
|
|
19421
|
-
const
|
|
19422
|
-
|
|
19423
|
-
|
|
19938
|
+
const idx = (y * cols + x) * 2;
|
|
19939
|
+
const gid = data2[idx];
|
|
19940
|
+
if (!gid) {
|
|
19941
|
+
continue;
|
|
19424
19942
|
}
|
|
19943
|
+
const flipMask = data2[idx + 1];
|
|
19944
|
+
if (!tilesetCache.contains(gid)) {
|
|
19945
|
+
tilesetCache = tilesets.getTilesetByGid(gid);
|
|
19946
|
+
}
|
|
19947
|
+
this.drawTileRaw(renderer2, x, y, gid, flipMask, tilesetCache);
|
|
19425
19948
|
}
|
|
19426
19949
|
}
|
|
19427
19950
|
vector2dPool.release(start);
|
|
@@ -19490,7 +20013,7 @@ var TMXObliqueRenderer = class extends TMXOrthogonalRenderer {
|
|
|
19490
20013
|
return ret.set(px + this.shearX * py, this.shearY * px + py);
|
|
19491
20014
|
}
|
|
19492
20015
|
/**
|
|
19493
|
-
* draw the tile map
|
|
20016
|
+
* draw the tile map (legacy entry point — accepts a fully-constructed Tile)
|
|
19494
20017
|
* @ignore
|
|
19495
20018
|
*/
|
|
19496
20019
|
drawTile(renderer2, x, y, tmxTile) {
|
|
@@ -19499,6 +20022,16 @@ var TMXObliqueRenderer = class extends TMXOrthogonalRenderer {
|
|
|
19499
20022
|
const dy = tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight + this.skewY * x;
|
|
19500
20023
|
tileset.drawTile(renderer2, dx, dy, tmxTile);
|
|
19501
20024
|
}
|
|
20025
|
+
/**
|
|
20026
|
+
* draw a tile from raw (gid, flipMask, tileset) data — used by the hot
|
|
20027
|
+
* rendering loop to bypass Tile construction
|
|
20028
|
+
* @ignore
|
|
20029
|
+
*/
|
|
20030
|
+
drawTileRaw(renderer2, x, y, gid, flipMask, tileset) {
|
|
20031
|
+
const dx = tileset.tileoffset.x + x * this.tilewidth + this.skewX * y;
|
|
20032
|
+
const dy = tileset.tileoffset.y + (y + 1) * this.tileheight - tileset.tileheight + this.skewY * x;
|
|
20033
|
+
tileset.drawTileRaw(renderer2, dx, dy, gid, flipMask);
|
|
20034
|
+
}
|
|
19502
20035
|
/**
|
|
19503
20036
|
* draw the given TMX Layer for the given area
|
|
19504
20037
|
* @ignore
|
|
@@ -19569,12 +20102,25 @@ var TMXObliqueRenderer = class extends TMXOrthogonalRenderer {
|
|
|
19569
20102
|
default:
|
|
19570
20103
|
break;
|
|
19571
20104
|
}
|
|
20105
|
+
const cols = layer.cols;
|
|
20106
|
+
const data2 = layer.layerData;
|
|
20107
|
+
const tilesets = layer.tilesets;
|
|
20108
|
+
let tilesetCache = layer.tileset;
|
|
20109
|
+
if (tilesetCache === null) {
|
|
20110
|
+
return;
|
|
20111
|
+
}
|
|
19572
20112
|
for (let y = startY; y !== endY; y += incY) {
|
|
19573
20113
|
for (let x = startX; x !== endX; x += incX) {
|
|
19574
|
-
const
|
|
19575
|
-
|
|
19576
|
-
|
|
20114
|
+
const idx = (y * cols + x) * 2;
|
|
20115
|
+
const gid = data2[idx];
|
|
20116
|
+
if (!gid) {
|
|
20117
|
+
continue;
|
|
19577
20118
|
}
|
|
20119
|
+
const flipMask = data2[idx + 1];
|
|
20120
|
+
if (!tilesetCache.contains(gid)) {
|
|
20121
|
+
tilesetCache = tilesets.getTilesetByGid(gid);
|
|
20122
|
+
}
|
|
20123
|
+
this.drawTileRaw(renderer2, x, y, gid, flipMask, tilesetCache);
|
|
19578
20124
|
}
|
|
19579
20125
|
}
|
|
19580
20126
|
}
|
|
@@ -19930,6 +20476,7 @@ var TMXGroup = class {
|
|
|
19930
20476
|
};
|
|
19931
20477
|
|
|
19932
20478
|
// src/level/tiled/TMXTileset.js
|
|
20479
|
+
var SCRATCH_MATRIX = new Matrix2d();
|
|
19933
20480
|
var TMXTileset = class {
|
|
19934
20481
|
/**
|
|
19935
20482
|
* @param {object} tileset - tileset data in JSON format ({@link http://docs.mapeditor.org/en/stable/reference/tmx-map-format/#tileset})
|
|
@@ -20260,6 +20807,96 @@ var TMXTileset = class {
|
|
|
20260
20807
|
renderer2.restore();
|
|
20261
20808
|
}
|
|
20262
20809
|
}
|
|
20810
|
+
/**
|
|
20811
|
+
* draw a tile at the specified position from raw (gid, flipMask) data
|
|
20812
|
+
*
|
|
20813
|
+
* Like {@link drawTile} but bypasses the {@link Tile} object entirely:
|
|
20814
|
+
* the renderer hot loop can pass the GID and flip mask straight from
|
|
20815
|
+
* `layer.layerData` without ever allocating a Tile instance.
|
|
20816
|
+
*
|
|
20817
|
+
* @param {CanvasRenderer|WebGLRenderer} renderer - a renderer instance
|
|
20818
|
+
* @param {number} dx - destination x position
|
|
20819
|
+
* @param {number} dy - destination y position
|
|
20820
|
+
* @param {number} gid - the tile's global id (with flip bits already stripped)
|
|
20821
|
+
* @param {number} flipMask - 3-bit packed flip mask (H=1, V=2, AD=4)
|
|
20822
|
+
* @ignore
|
|
20823
|
+
*/
|
|
20824
|
+
drawTileRaw(renderer2, dx, dy, gid, flipMask) {
|
|
20825
|
+
let dw, dh;
|
|
20826
|
+
let tileImage;
|
|
20827
|
+
if (this.isCollection) {
|
|
20828
|
+
tileImage = this.imageCollection.get(gid);
|
|
20829
|
+
const tileWidth = tileImage.width;
|
|
20830
|
+
const tileHeight = tileImage.height;
|
|
20831
|
+
if (this.tilerendersize === "grid") {
|
|
20832
|
+
let scaleX = this.mapTilewidth / tileWidth;
|
|
20833
|
+
let scaleY = this.mapTileheight / tileHeight;
|
|
20834
|
+
if (this.fillmode === "preserve-aspect-fit") {
|
|
20835
|
+
const scale2 = Math.min(scaleX, scaleY);
|
|
20836
|
+
scaleX = scale2;
|
|
20837
|
+
scaleY = scale2;
|
|
20838
|
+
}
|
|
20839
|
+
dw = tileWidth * scaleX;
|
|
20840
|
+
dh = tileHeight * scaleY;
|
|
20841
|
+
dy += this.tileheight - dh;
|
|
20842
|
+
if (this.fillmode === "preserve-aspect-fit") {
|
|
20843
|
+
dx += (this.mapTilewidth - dw) / 2;
|
|
20844
|
+
dy -= (this.mapTileheight - dh) / 2;
|
|
20845
|
+
}
|
|
20846
|
+
} else {
|
|
20847
|
+
dw = tileWidth;
|
|
20848
|
+
dh = tileHeight;
|
|
20849
|
+
}
|
|
20850
|
+
} else {
|
|
20851
|
+
dw = this._renderDw;
|
|
20852
|
+
dh = this._renderDh;
|
|
20853
|
+
dy += this._renderDyOffset;
|
|
20854
|
+
dx += this._renderDxCenter;
|
|
20855
|
+
dy += this._renderDyCenter;
|
|
20856
|
+
}
|
|
20857
|
+
if (flipMask !== 0) {
|
|
20858
|
+
renderer2.save();
|
|
20859
|
+
renderer2.translate(dx, dy);
|
|
20860
|
+
renderer2.transform(
|
|
20861
|
+
buildFlipTransform(
|
|
20862
|
+
SCRATCH_MATRIX,
|
|
20863
|
+
flipMask,
|
|
20864
|
+
this.isCollection ? tileImage.width : this.tilewidth,
|
|
20865
|
+
this.isCollection ? tileImage.height : this.tileheight
|
|
20866
|
+
)
|
|
20867
|
+
);
|
|
20868
|
+
dx = dy = 0;
|
|
20869
|
+
}
|
|
20870
|
+
if (this.isCollection) {
|
|
20871
|
+
renderer2.drawImage(
|
|
20872
|
+
tileImage,
|
|
20873
|
+
0,
|
|
20874
|
+
0,
|
|
20875
|
+
tileImage.width,
|
|
20876
|
+
tileImage.height,
|
|
20877
|
+
dx,
|
|
20878
|
+
dy,
|
|
20879
|
+
dw,
|
|
20880
|
+
dh
|
|
20881
|
+
);
|
|
20882
|
+
} else {
|
|
20883
|
+
const offset = this.atlas[this.getViewTileId(gid)].offset;
|
|
20884
|
+
renderer2.drawImage(
|
|
20885
|
+
this.image,
|
|
20886
|
+
offset.x,
|
|
20887
|
+
offset.y,
|
|
20888
|
+
this.tilewidth,
|
|
20889
|
+
this.tileheight,
|
|
20890
|
+
dx,
|
|
20891
|
+
dy,
|
|
20892
|
+
dw + renderer2.uvOffset,
|
|
20893
|
+
dh + renderer2.uvOffset
|
|
20894
|
+
);
|
|
20895
|
+
}
|
|
20896
|
+
if (flipMask !== 0) {
|
|
20897
|
+
renderer2.restore();
|
|
20898
|
+
}
|
|
20899
|
+
}
|
|
20263
20900
|
};
|
|
20264
20901
|
|
|
20265
20902
|
// src/level/tiled/TMXTilesetGroup.js
|
|
@@ -20378,6 +21015,14 @@ function readTileset(data2, mapTilewidth, mapTileheight) {
|
|
|
20378
21015
|
function readObjectGroup(map, data2, z) {
|
|
20379
21016
|
return new TMXGroup(map, data2, z);
|
|
20380
21017
|
}
|
|
21018
|
+
function refreshAbsoluteBounds(container) {
|
|
21019
|
+
container.forEach((child) => {
|
|
21020
|
+
child.updateBounds(true);
|
|
21021
|
+
if (child instanceof Container) {
|
|
21022
|
+
refreshAbsoluteBounds(child);
|
|
21023
|
+
}
|
|
21024
|
+
});
|
|
21025
|
+
}
|
|
20381
21026
|
var TMXTileMap = class {
|
|
20382
21027
|
/**
|
|
20383
21028
|
* @param {string} levelId - name of TMX map
|
|
@@ -20539,12 +21184,12 @@ var TMXTileMap = class {
|
|
|
20539
21184
|
Math.max(levelBounds.width, width),
|
|
20540
21185
|
Math.max(levelBounds.height, height)
|
|
20541
21186
|
);
|
|
20542
|
-
|
|
20543
|
-
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
container
|
|
20547
|
-
|
|
21187
|
+
const newX = Math.max(0, ~~((width - levelBounds.width) / 2));
|
|
21188
|
+
const newY = Math.max(0, ~~((height - levelBounds.height) / 2));
|
|
21189
|
+
if (container.pos.x !== newX || container.pos.y !== newY) {
|
|
21190
|
+
container.pos.set(newX, newY, container.pos.z);
|
|
21191
|
+
refreshAbsoluteBounds(container);
|
|
21192
|
+
}
|
|
20548
21193
|
};
|
|
20549
21194
|
var _setBounds = _setBounds2;
|
|
20550
21195
|
const app = container.getRootAncestor().app;
|
|
@@ -21354,6 +21999,7 @@ var Sprite = class extends Renderable {
|
|
|
21354
21999
|
* @param {number} [settings.flipX] - flip the sprite on the horizontal axis
|
|
21355
22000
|
* @param {number} [settings.flipY] - flip the sprite on the vertical axis
|
|
21356
22001
|
* @param {Vector2d} [settings.anchorPoint={x:0.5, y:0.5}] - Anchor point to draw the frame at (defaults to the center of the frame).
|
|
22002
|
+
* @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
22003
|
* @example
|
|
21358
22004
|
* // create a single sprite from a standalone image, with anchor in the center
|
|
21359
22005
|
* let sprite = new me.Sprite(0, 0, {
|
|
@@ -21390,6 +22036,7 @@ var Sprite = class extends Renderable {
|
|
|
21390
22036
|
this.offset = vector2dPool.get(0, 0);
|
|
21391
22037
|
this.isVideo = false;
|
|
21392
22038
|
this.source = null;
|
|
22039
|
+
this._normalMap = null;
|
|
21393
22040
|
this.anim = {};
|
|
21394
22041
|
this.resetAnim = void 0;
|
|
21395
22042
|
this.current = {
|
|
@@ -21463,6 +22110,25 @@ var Sprite = class extends Renderable {
|
|
|
21463
22110
|
this.textureAtlas = this.source.getAtlas();
|
|
21464
22111
|
}
|
|
21465
22112
|
}
|
|
22113
|
+
if (settings.image instanceof TextureAtlas && typeof settings.image.getNormalTexture === "function") {
|
|
22114
|
+
const fromAtlas = settings.image.getNormalTexture();
|
|
22115
|
+
if (fromAtlas) {
|
|
22116
|
+
this.normalMap = fromAtlas;
|
|
22117
|
+
}
|
|
22118
|
+
}
|
|
22119
|
+
if (this.normalMap === null && typeof settings.normalMap !== "undefined" && settings.normalMap !== null) {
|
|
22120
|
+
if (typeof settings.normalMap === "string") {
|
|
22121
|
+
const resolved = getImage(settings.normalMap);
|
|
22122
|
+
if (!resolved) {
|
|
22123
|
+
throw new Error(
|
|
22124
|
+
"me.Sprite: '" + settings.normalMap + "' normal map image not found!"
|
|
22125
|
+
);
|
|
22126
|
+
}
|
|
22127
|
+
this.normalMap = resolved;
|
|
22128
|
+
} else {
|
|
22129
|
+
this.normalMap = settings.normalMap;
|
|
22130
|
+
}
|
|
22131
|
+
}
|
|
21466
22132
|
if (typeof settings.atlas !== "undefined") {
|
|
21467
22133
|
this.textureAtlas = settings.atlas;
|
|
21468
22134
|
this.atlasIndices = settings.atlasIndices;
|
|
@@ -21505,6 +22171,37 @@ var Sprite = class extends Renderable {
|
|
|
21505
22171
|
this.setCurrentAnimation("default");
|
|
21506
22172
|
}
|
|
21507
22173
|
}
|
|
22174
|
+
/**
|
|
22175
|
+
* The optional normal-map image paired with this sprite's color
|
|
22176
|
+
* texture (SpriteIlluminator workflow). When set, the WebGL
|
|
22177
|
+
* renderer's lit pipeline samples this texture for per-pixel
|
|
22178
|
+
* lighting using `Stage._activeLights`. `null` when unlit.
|
|
22179
|
+
* Setting any non-image value (or anything without numeric
|
|
22180
|
+
* `width`/`height`) throws — assign `null` to clear.
|
|
22181
|
+
*
|
|
22182
|
+
* Silently ignored by the Canvas renderer.
|
|
22183
|
+
* @type {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap|null}
|
|
22184
|
+
*/
|
|
22185
|
+
get normalMap() {
|
|
22186
|
+
return this._normalMap;
|
|
22187
|
+
}
|
|
22188
|
+
set normalMap(value) {
|
|
22189
|
+
if (value === null || value === void 0) {
|
|
22190
|
+
this._normalMap = null;
|
|
22191
|
+
return;
|
|
22192
|
+
}
|
|
22193
|
+
if (typeof value !== "object" || typeof value.width !== "number" || typeof value.height !== "number") {
|
|
22194
|
+
throw new TypeError(
|
|
22195
|
+
"Sprite.normalMap must be null or an image-like object with numeric width/height (HTMLImageElement, HTMLCanvasElement, OffscreenCanvas, ImageBitmap)"
|
|
22196
|
+
);
|
|
22197
|
+
}
|
|
22198
|
+
if (typeof value.videoWidth === "number") {
|
|
22199
|
+
throw new TypeError(
|
|
22200
|
+
"Sprite.normalMap does not support HTMLVideoElement (the lit pipeline caches the texture per image reference and would freeze on frame 0)"
|
|
22201
|
+
);
|
|
22202
|
+
}
|
|
22203
|
+
this._normalMap = value;
|
|
22204
|
+
}
|
|
21508
22205
|
/**
|
|
21509
22206
|
* return the flickering state of the object
|
|
21510
22207
|
* @returns {boolean}
|
|
@@ -21854,9 +22551,32 @@ var Sprite = class extends Renderable {
|
|
|
21854
22551
|
}
|
|
21855
22552
|
return super.update(dt);
|
|
21856
22553
|
}
|
|
22554
|
+
/**
|
|
22555
|
+
* Prepare the rendering context before drawing this sprite (automatically called by melonJS).
|
|
22556
|
+
* Extends `Renderable.preDraw` to publish this sprite's `normalMap` (if any)
|
|
22557
|
+
* on the renderer so the WebGL lit pipeline can pair it with the next
|
|
22558
|
+
* `drawImage` call. Cleared back in `postDraw`.
|
|
22559
|
+
* @param {Renderer} renderer - a renderer instance
|
|
22560
|
+
*/
|
|
22561
|
+
preDraw(renderer2) {
|
|
22562
|
+
super.preDraw(renderer2);
|
|
22563
|
+
if (this._normalMap !== null) {
|
|
22564
|
+
renderer2.currentNormalMap = this._normalMap;
|
|
22565
|
+
}
|
|
22566
|
+
}
|
|
22567
|
+
/**
|
|
22568
|
+
* restore the rendering context after drawing this sprite (automatically called by melonJS).
|
|
22569
|
+
* @param {Renderer} renderer - a renderer instance
|
|
22570
|
+
*/
|
|
22571
|
+
postDraw(renderer2) {
|
|
22572
|
+
if (this._normalMap !== null) {
|
|
22573
|
+
renderer2.currentNormalMap = null;
|
|
22574
|
+
}
|
|
22575
|
+
super.postDraw(renderer2);
|
|
22576
|
+
}
|
|
21857
22577
|
/**
|
|
21858
22578
|
* draw this sprite (automatically called by melonJS)
|
|
21859
|
-
* @param {
|
|
22579
|
+
* @param {Renderer} renderer - a renderer instance
|
|
21860
22580
|
* @param {Camera2d} [viewport] - the viewport to (re)draw
|
|
21861
22581
|
*/
|
|
21862
22582
|
draw(renderer2) {
|
|
@@ -21915,6 +22635,7 @@ var Sprite = class extends Renderable {
|
|
|
21915
22635
|
this.image.currentTime = 0;
|
|
21916
22636
|
}
|
|
21917
22637
|
this.image = void 0;
|
|
22638
|
+
this._normalMap = null;
|
|
21918
22639
|
super.destroy();
|
|
21919
22640
|
}
|
|
21920
22641
|
};
|
|
@@ -22108,7 +22829,7 @@ var ColorMatrix = class extends Matrix3d {
|
|
|
22108
22829
|
// src/video/webgl/utils/attributes.js
|
|
22109
22830
|
function extractAttributes(gl, shader) {
|
|
22110
22831
|
const attributes = {};
|
|
22111
|
-
const attrRx = /attribute\
|
|
22832
|
+
const attrRx = /(?:^|\n)[ \t]*(?:attribute|in)[ \t]+(?:\w+[ \t]+)+(\w+)/g;
|
|
22112
22833
|
let match;
|
|
22113
22834
|
let i = 0;
|
|
22114
22835
|
while (match = attrRx.exec(shader.vertex)) {
|
|
@@ -22119,10 +22840,18 @@ function extractAttributes(gl, shader) {
|
|
|
22119
22840
|
|
|
22120
22841
|
// src/video/webgl/utils/precision.js
|
|
22121
22842
|
function setPrecision(src, precision) {
|
|
22122
|
-
if (
|
|
22123
|
-
return
|
|
22843
|
+
if (/^\s*(?:#version[^\n]*\n)?\s*precision\b/.test(src)) {
|
|
22844
|
+
return src;
|
|
22124
22845
|
}
|
|
22125
|
-
|
|
22846
|
+
if (src.substring(0, 8) === "#version") {
|
|
22847
|
+
const inject = "\nprecision " + precision + " float;\nprecision " + precision + " int;";
|
|
22848
|
+
const nl = src.indexOf("\n");
|
|
22849
|
+
if (nl < 0) {
|
|
22850
|
+
return src + inject;
|
|
22851
|
+
}
|
|
22852
|
+
return src.substring(0, nl) + inject + src.substring(nl);
|
|
22853
|
+
}
|
|
22854
|
+
return "precision " + precision + " float;\n" + src;
|
|
22126
22855
|
}
|
|
22127
22856
|
function getMaxShaderPrecision(gl, highPrecision = true) {
|
|
22128
22857
|
if (highPrecision && gl.getShaderPrecisionFormat(gl.VERTEX_SHADER, gl.HIGH_FLOAT).precision > 0 && gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).precision > 0) {
|
|
@@ -22188,17 +22917,56 @@ var fnHash = {
|
|
|
22188
22917
|
ivec2: "2iv",
|
|
22189
22918
|
ivec3: "3iv",
|
|
22190
22919
|
ivec4: "4iv",
|
|
22920
|
+
uvec2: "2uiv",
|
|
22921
|
+
uvec3: "3uiv",
|
|
22922
|
+
uvec4: "4uiv",
|
|
22191
22923
|
mat2: "Matrix2fv",
|
|
22192
22924
|
mat3: "Matrix3fv",
|
|
22193
22925
|
mat4: "Matrix4fv",
|
|
22194
|
-
sampler2D: "1i"
|
|
22926
|
+
sampler2D: "1i",
|
|
22927
|
+
// WebGL2 integer-typed samplers — bound to a unit just like a
|
|
22928
|
+
// `sampler2D`; the GLSL `usampler2D` / `isampler2D` types let the
|
|
22929
|
+
// shader read raw integer values via `texelFetch` instead of the
|
|
22930
|
+
// normalized-float `texture()` path.
|
|
22931
|
+
usampler2D: "1i",
|
|
22932
|
+
isampler2D: "1i"
|
|
22195
22933
|
};
|
|
22934
|
+
function valuesMatch(cached, val) {
|
|
22935
|
+
if (cached === void 0) {
|
|
22936
|
+
return false;
|
|
22937
|
+
}
|
|
22938
|
+
if (val !== null && typeof val === "object" && typeof val.length === "number") {
|
|
22939
|
+
if (cached.length !== val.length) {
|
|
22940
|
+
return false;
|
|
22941
|
+
}
|
|
22942
|
+
for (let i = 0; i < val.length; i++) {
|
|
22943
|
+
if (cached[i] !== val[i]) {
|
|
22944
|
+
return false;
|
|
22945
|
+
}
|
|
22946
|
+
}
|
|
22947
|
+
return true;
|
|
22948
|
+
}
|
|
22949
|
+
return cached === val;
|
|
22950
|
+
}
|
|
22951
|
+
function captureValue(prev, val) {
|
|
22952
|
+
if (val === null || typeof val !== "object" || typeof val.length !== "number") {
|
|
22953
|
+
return val;
|
|
22954
|
+
}
|
|
22955
|
+
if (prev !== void 0 && typeof prev === "object" && typeof prev.length === "number" && prev.length === val.length) {
|
|
22956
|
+
for (let i = 0; i < val.length; i++) {
|
|
22957
|
+
prev[i] = val[i];
|
|
22958
|
+
}
|
|
22959
|
+
return prev;
|
|
22960
|
+
}
|
|
22961
|
+
return typeof val.slice === "function" ? val.slice() : Array.from(val);
|
|
22962
|
+
}
|
|
22196
22963
|
function extractUniforms(gl, shader) {
|
|
22197
22964
|
const uniforms = {};
|
|
22198
22965
|
const uniRx = /uniform\s+(\w+)\s+(\w+)/g;
|
|
22199
22966
|
const uniformsData = {};
|
|
22200
22967
|
const descriptor = {};
|
|
22201
22968
|
const locations = {};
|
|
22969
|
+
const cache2 = {};
|
|
22202
22970
|
let match;
|
|
22203
22971
|
[shader.vertex, shader.fragment].forEach((shader2) => {
|
|
22204
22972
|
while (match = uniRx.exec(shader2)) {
|
|
@@ -22217,6 +22985,10 @@ function extractUniforms(gl, shader) {
|
|
|
22217
22985
|
set: (function(name2, type2, fn) {
|
|
22218
22986
|
if (/^mat/.test(type2)) {
|
|
22219
22987
|
return function(val) {
|
|
22988
|
+
if (valuesMatch(cache2[name2], val)) {
|
|
22989
|
+
return;
|
|
22990
|
+
}
|
|
22991
|
+
cache2[name2] = captureValue(cache2[name2], val);
|
|
22220
22992
|
gl[fn](locations[name2], false, val);
|
|
22221
22993
|
};
|
|
22222
22994
|
} else {
|
|
@@ -22225,6 +22997,10 @@ function extractUniforms(gl, shader) {
|
|
|
22225
22997
|
if (val.length && !/v$/.test(fn)) {
|
|
22226
22998
|
fnv += "v";
|
|
22227
22999
|
}
|
|
23000
|
+
if (valuesMatch(cache2[name2], val)) {
|
|
23001
|
+
return;
|
|
23002
|
+
}
|
|
23003
|
+
cache2[name2] = captureValue(cache2[name2], val);
|
|
22228
23004
|
gl[fnv](locations[name2], val);
|
|
22229
23005
|
};
|
|
22230
23006
|
}
|
|
@@ -22343,8 +23119,6 @@ var GLShader = class {
|
|
|
22343
23119
|
stride,
|
|
22344
23120
|
element.offset
|
|
22345
23121
|
);
|
|
22346
|
-
} else {
|
|
22347
|
-
gl.disableVertexAttribArray(index);
|
|
22348
23122
|
}
|
|
22349
23123
|
}
|
|
22350
23124
|
}
|
|
@@ -23308,6 +24082,13 @@ var Camera2d = class extends Renderable {
|
|
|
23308
24082
|
} else {
|
|
23309
24083
|
renderer2.setProjection(this.projectionMatrix);
|
|
23310
24084
|
}
|
|
24085
|
+
const stage = state_default.current();
|
|
24086
|
+
renderer2.setLightUniforms(
|
|
24087
|
+
stage?._activeLights,
|
|
24088
|
+
stage?.ambientLightingColor,
|
|
24089
|
+
translateX,
|
|
24090
|
+
translateY
|
|
24091
|
+
);
|
|
23311
24092
|
container.preDraw(r);
|
|
23312
24093
|
if (isNonDefault) {
|
|
23313
24094
|
const view = this.worldView;
|
|
@@ -23324,6 +24105,9 @@ var Camera2d = class extends Renderable {
|
|
|
23324
24105
|
this.drawFX(renderer2);
|
|
23325
24106
|
container.postDraw(r);
|
|
23326
24107
|
this.postDraw(r);
|
|
24108
|
+
if (stage) {
|
|
24109
|
+
stage.drawLighting(renderer2, this, translateX, translateY);
|
|
24110
|
+
}
|
|
23327
24111
|
r.endPostEffect(this);
|
|
23328
24112
|
if (this._colorMatrixEffect) {
|
|
23329
24113
|
const idx = this.postEffects.indexOf(this._colorMatrixEffect);
|
|
@@ -23361,28 +24145,50 @@ var Stage = class {
|
|
|
23361
24145
|
cameras;
|
|
23362
24146
|
/**
|
|
23363
24147
|
* The list of active lights in this stage.
|
|
23364
|
-
*
|
|
24148
|
+
*
|
|
24149
|
+
* Since 19.3.0, `Light2d` is a first-class world Renderable — the
|
|
24150
|
+
* recommended pattern is to add lights directly to `app.world` (or any
|
|
24151
|
+
* container, including a sprite, so the light follows it via parent
|
|
24152
|
+
* transforms). The `lights` Map remains for backward compatibility:
|
|
24153
|
+
* any entry added via `this.lights.set(name, light)` in
|
|
24154
|
+
* `onResetEvent()` is automatically adopted into the world tree at
|
|
24155
|
+
* stage reset time so it renders normally.
|
|
23365
24156
|
* @see Light2d
|
|
23366
24157
|
* @see Stage.ambientLight
|
|
23367
24158
|
* @example
|
|
23368
|
-
* //
|
|
23369
|
-
* const whiteLight = new Light2d(
|
|
23370
|
-
*
|
|
24159
|
+
* // recommended:
|
|
24160
|
+
* const whiteLight = new Light2d(100, 100, 140, 140, "#fff", 0.7);
|
|
24161
|
+
* app.world.addChild(whiteLight);
|
|
24162
|
+
*
|
|
24163
|
+
* // legacy (still works, auto-adopted into world):
|
|
23371
24164
|
* this.lights.set("whiteLight", whiteLight);
|
|
23372
|
-
*
|
|
24165
|
+
*
|
|
23373
24166
|
* 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
24167
|
*/
|
|
23379
24168
|
lights;
|
|
24169
|
+
/**
|
|
24170
|
+
* Internal set of active lights, auto-populated by `Light2d`'s
|
|
24171
|
+
* `onActivateEvent` / `onDeactivateEvent` hooks. Used by Camera2d's
|
|
24172
|
+
* ambient-overlay pass to compute the cutouts.
|
|
24173
|
+
* @ignore
|
|
24174
|
+
*/
|
|
24175
|
+
_activeLights;
|
|
23380
24176
|
/**
|
|
23381
24177
|
* an ambient light that will be added to the stage rendering
|
|
23382
24178
|
* @default "#000000"
|
|
23383
24179
|
* @see Light2d
|
|
23384
24180
|
*/
|
|
23385
24181
|
ambientLight;
|
|
24182
|
+
/**
|
|
24183
|
+
* Base light level applied to every normal-mapped sprite in the
|
|
24184
|
+
* lit rendering path. Unlike {@link Stage#ambientLight} (which is
|
|
24185
|
+
* the dark overlay punched by each light's cutout), this color is
|
|
24186
|
+
* added to every lit pixel so unlit areas don't render pure
|
|
24187
|
+
* black. Defaults to black (0, 0, 0) — sprites without a
|
|
24188
|
+
* `normalMap` ignore it entirely.
|
|
24189
|
+
* @default "#000000"
|
|
24190
|
+
*/
|
|
24191
|
+
ambientLightingColor;
|
|
23386
24192
|
/**
|
|
23387
24193
|
* The given constructor options
|
|
23388
24194
|
*/
|
|
@@ -23396,9 +24202,26 @@ var Stage = class {
|
|
|
23396
24202
|
constructor(settings) {
|
|
23397
24203
|
this.cameras = /* @__PURE__ */ new Map();
|
|
23398
24204
|
this.lights = /* @__PURE__ */ new Map();
|
|
24205
|
+
this._activeLights = /* @__PURE__ */ new Set();
|
|
23399
24206
|
this.ambientLight = new Color(0, 0, 0, 0);
|
|
24207
|
+
this.ambientLightingColor = new Color(0, 0, 0, 1);
|
|
23400
24208
|
this.settings = Object.assign({}, default_settings, settings || {});
|
|
23401
24209
|
}
|
|
24210
|
+
/**
|
|
24211
|
+
* Called by `Light2d.onActivateEvent` to register the light with the
|
|
24212
|
+
* stage's ambient-overlay cutout list. Users normally don't call this.
|
|
24213
|
+
* @ignore
|
|
24214
|
+
*/
|
|
24215
|
+
_registerLight(light) {
|
|
24216
|
+
this._activeLights.add(light);
|
|
24217
|
+
}
|
|
24218
|
+
/**
|
|
24219
|
+
* Called by `Light2d.onDeactivateEvent` to deregister the light.
|
|
24220
|
+
* @ignore
|
|
24221
|
+
*/
|
|
24222
|
+
_unregisterLight(light) {
|
|
24223
|
+
this._activeLights.delete(light);
|
|
24224
|
+
}
|
|
23402
24225
|
/**
|
|
23403
24226
|
* Object reset function
|
|
23404
24227
|
* @ignore
|
|
@@ -23419,6 +24242,13 @@ var Stage = class {
|
|
|
23419
24242
|
}
|
|
23420
24243
|
emit(STAGE_RESET, this);
|
|
23421
24244
|
this.onResetEvent(app, ...extraArgs);
|
|
24245
|
+
if (app && app.world) {
|
|
24246
|
+
this.lights.forEach((light) => {
|
|
24247
|
+
if (!light.ancestor) {
|
|
24248
|
+
app.world.addChild(light);
|
|
24249
|
+
}
|
|
24250
|
+
});
|
|
24251
|
+
}
|
|
23422
24252
|
}
|
|
23423
24253
|
/**
|
|
23424
24254
|
* update function
|
|
@@ -23433,40 +24263,60 @@ var Stage = class {
|
|
|
23433
24263
|
isDirty = true;
|
|
23434
24264
|
}
|
|
23435
24265
|
});
|
|
23436
|
-
this.lights.forEach((light) => {
|
|
23437
|
-
if (light.update()) {
|
|
23438
|
-
isDirty = true;
|
|
23439
|
-
}
|
|
23440
|
-
});
|
|
23441
24266
|
return isDirty;
|
|
23442
24267
|
}
|
|
23443
24268
|
/**
|
|
23444
24269
|
* draw the current stage
|
|
24270
|
+
*
|
|
24271
|
+
* Lights are rendered as part of the world tree (they're now first-class
|
|
24272
|
+
* Renderables) and the ambient overlay pass runs inside each Camera's
|
|
24273
|
+
* post-effect FBO bracket via {@link Stage#drawLighting}.
|
|
23445
24274
|
* @ignore
|
|
23446
24275
|
* @param renderer - the renderer object to draw with
|
|
23447
24276
|
* @param world - the world object to draw
|
|
23448
24277
|
*/
|
|
23449
24278
|
draw(renderer2, world) {
|
|
23450
|
-
const r = renderer2;
|
|
23451
24279
|
this.cameras.forEach((camera) => {
|
|
23452
24280
|
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
24281
|
});
|
|
23469
24282
|
}
|
|
24283
|
+
/**
|
|
24284
|
+
* Draw the stage's ambient-light overlay with cutouts for each active
|
|
24285
|
+
* light. Called from each `Camera2d` inside its post-effect FBO bracket —
|
|
24286
|
+
* lights themselves render via the world tree (they're standard
|
|
24287
|
+
* Renderables); this pass only paints the dark fill that the lights cut
|
|
24288
|
+
* holes through.
|
|
24289
|
+
*
|
|
24290
|
+
* Subclasses can override this method to implement custom lighting (e.g.
|
|
24291
|
+
* per-pixel normal-mapped lighting via a custom shader). Called once per
|
|
24292
|
+
* camera per frame.
|
|
24293
|
+
* @param renderer - the active renderer
|
|
24294
|
+
* @param camera - the camera currently rendering this stage
|
|
24295
|
+
* @param translateX - the same world-to-screen X translate that
|
|
24296
|
+
* `Camera2d.draw()` applies to the world container (i.e.
|
|
24297
|
+
* `camera.pos.x + camera.offset.x` for the default camera, plus
|
|
24298
|
+
* the container's own offset for non-default cameras)
|
|
24299
|
+
* @param translateY - the world-to-screen Y translate (see `translateX`)
|
|
24300
|
+
*/
|
|
24301
|
+
drawLighting(renderer2, camera, translateX = camera.pos.x + camera.offset.x, translateY = camera.pos.y + camera.offset.y) {
|
|
24302
|
+
if (this.ambientLight.alpha === 0) {
|
|
24303
|
+
return;
|
|
24304
|
+
}
|
|
24305
|
+
const r = renderer2;
|
|
24306
|
+
r.save();
|
|
24307
|
+
const tx = translateX;
|
|
24308
|
+
const ty = translateY;
|
|
24309
|
+
if (tx !== 0 || ty !== 0) {
|
|
24310
|
+
r.translate(-tx, -ty);
|
|
24311
|
+
}
|
|
24312
|
+
this._activeLights.forEach((light) => {
|
|
24313
|
+
r.setMask(light.getVisibleArea(), true);
|
|
24314
|
+
});
|
|
24315
|
+
r.setColor(this.ambientLight);
|
|
24316
|
+
r.fillRect(tx, ty, camera.width, camera.height);
|
|
24317
|
+
r.clearMask();
|
|
24318
|
+
r.restore();
|
|
24319
|
+
}
|
|
23470
24320
|
/**
|
|
23471
24321
|
* destroy function
|
|
23472
24322
|
* @ignore
|
|
@@ -23474,9 +24324,12 @@ var Stage = class {
|
|
|
23474
24324
|
destroy(app) {
|
|
23475
24325
|
this.cameras.clear();
|
|
23476
24326
|
this.lights.forEach((light) => {
|
|
23477
|
-
light.
|
|
24327
|
+
if (!light.ancestor) {
|
|
24328
|
+
light.destroy();
|
|
24329
|
+
}
|
|
23478
24330
|
});
|
|
23479
24331
|
this.lights.clear();
|
|
24332
|
+
this._activeLights.clear();
|
|
23480
24333
|
this.onDestroyEvent(app);
|
|
23481
24334
|
}
|
|
23482
24335
|
/**
|
|
@@ -26798,13 +27651,9 @@ var ImageLayer = class extends Sprite {
|
|
|
26798
27651
|
if (this.mask) {
|
|
26799
27652
|
renderer2.setMask(this.mask);
|
|
26800
27653
|
}
|
|
26801
|
-
|
|
26802
|
-
|
|
26803
|
-
|
|
26804
|
-
0,
|
|
26805
|
-
viewport.width * 2,
|
|
26806
|
-
viewport.height * 2
|
|
26807
|
-
);
|
|
27654
|
+
const drawW = this.repeatX ? viewport.width * 2 : width;
|
|
27655
|
+
const drawH = this.repeatY ? viewport.height * 2 : height;
|
|
27656
|
+
renderer2.drawPattern(this._pattern, 0, 0, drawW, drawH);
|
|
26808
27657
|
}
|
|
26809
27658
|
// called when the layer is removed from the game world or a container
|
|
26810
27659
|
onDeactivateEvent() {
|
|
@@ -26825,64 +27674,33 @@ var ImageLayer = class extends Sprite {
|
|
|
26825
27674
|
};
|
|
26826
27675
|
|
|
26827
27676
|
// 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
27677
|
var Light2d = class extends Renderable {
|
|
26879
27678
|
/**
|
|
26880
|
-
*
|
|
26881
|
-
*
|
|
27679
|
+
* Create a 2D point light.
|
|
27680
|
+
*
|
|
27681
|
+
* A `Light2d` is a first-class world Renderable: add it to a container
|
|
27682
|
+
* with `app.world.addChild(light)` (or any sub-container, including a
|
|
27683
|
+
* `Sprite`, so the light follows the parent via its transform). On
|
|
27684
|
+
* activation, the light auto-registers with the active `Stage`'s
|
|
27685
|
+
* lighting set so the ambient overlay (`Stage.ambientLight`) cuts a
|
|
27686
|
+
* hole at the light's visible area, and a radial gradient from the
|
|
27687
|
+
* given `color` (full intensity at center → fully transparent at the
|
|
27688
|
+
* radius) is composited additively on top — producing a soft spot
|
|
27689
|
+
* light. Rendering happens inside each `Camera2d`'s post-effect FBO
|
|
27690
|
+
* bracket so any camera shader (vignette, color-matrix, scanlines,
|
|
27691
|
+
* etc.) wraps the lighting output.
|
|
27692
|
+
*
|
|
27693
|
+
* Set `radiusY` to a different value than `radiusX` for a stretched
|
|
27694
|
+
* (elliptical) light. The `intensity` parameter scales the gradient's
|
|
27695
|
+
* inner alpha; the `Stage.ambientLight` color and alpha control how
|
|
27696
|
+
* dark the unlit areas are. Use `light.blendMode` to override the
|
|
27697
|
+
* default additive blend if needed.
|
|
27698
|
+
* @param {number} x - The horizontal position of the light's center (matches `Ellipse(x, y, w, h)` conventions).
|
|
27699
|
+
* @param {number} y - The vertical position of the light's center.
|
|
26882
27700
|
* @param {number} radiusX - The horizontal radius of the light.
|
|
26883
27701
|
* @param {number} [radiusY=radiusX] - The vertical radius of the light.
|
|
26884
|
-
* @param {Color|string} [color="#FFF"] -
|
|
26885
|
-
* @param {number} [intensity=0.7] - The
|
|
27702
|
+
* @param {Color|string} [color="#FFF"] - The color of the light at full intensity.
|
|
27703
|
+
* @param {number} [intensity=0.7] - The peak alpha of the radial gradient at the light's center (0–1).
|
|
26886
27704
|
*/
|
|
26887
27705
|
constructor(x, y, radiusX, radiusY = radiusX, color = "#FFF", intensity = 0.7) {
|
|
26888
27706
|
super(x, y, radiusX * 2, radiusY * 2);
|
|
@@ -26892,32 +27710,77 @@ var Light2d = class extends Renderable {
|
|
|
26892
27710
|
this.intensity = intensity;
|
|
26893
27711
|
this.blendMode = "lighter";
|
|
26894
27712
|
this.visibleArea = ellipsePool.get(
|
|
26895
|
-
this.
|
|
26896
|
-
this.
|
|
27713
|
+
this.pos.x,
|
|
27714
|
+
this.pos.y,
|
|
26897
27715
|
this.width,
|
|
26898
27716
|
this.height
|
|
26899
27717
|
);
|
|
26900
|
-
this.
|
|
26901
|
-
|
|
26902
|
-
|
|
26903
|
-
|
|
26904
|
-
|
|
27718
|
+
this.anchorPoint.set(0.5, 0.5);
|
|
27719
|
+
this.illuminationOnly = false;
|
|
27720
|
+
this.lightHeight = Math.max(radiusX, radiusY) * 0.075;
|
|
27721
|
+
}
|
|
27722
|
+
/**
|
|
27723
|
+
* the horizontal coordinate of this light's center.
|
|
27724
|
+
* Overrides Rect's getter, which assumes `pos` is the bbox top-left and
|
|
27725
|
+
* returns `pos.x + width/2`. Light2d uses `anchorPoint = (0.5, 0.5)`, so
|
|
27726
|
+
* `pos` already IS the center.
|
|
27727
|
+
* @type {number}
|
|
27728
|
+
*/
|
|
27729
|
+
get centerX() {
|
|
27730
|
+
return this.pos.x;
|
|
27731
|
+
}
|
|
27732
|
+
set centerX(value) {
|
|
27733
|
+
this.pos.x = value;
|
|
27734
|
+
this.recalc();
|
|
27735
|
+
this.updateBounds();
|
|
27736
|
+
}
|
|
27737
|
+
/**
|
|
27738
|
+
* the vertical coordinate of this light's center.
|
|
27739
|
+
* @see Light2d#centerX
|
|
27740
|
+
* @type {number}
|
|
27741
|
+
*/
|
|
27742
|
+
get centerY() {
|
|
27743
|
+
return this.pos.y;
|
|
27744
|
+
}
|
|
27745
|
+
set centerY(value) {
|
|
27746
|
+
this.pos.y = value;
|
|
27747
|
+
this.recalc();
|
|
27748
|
+
this.updateBounds();
|
|
26905
27749
|
}
|
|
26906
27750
|
/**
|
|
26907
|
-
*
|
|
27751
|
+
* Set new radii for this light.
|
|
27752
|
+
*
|
|
27753
|
+
* Updates `radiusX`/`radiusY` and the underlying bbox (via
|
|
27754
|
+
* `Renderable.resize(width, height)`) so `getBounds()` and
|
|
27755
|
+
* `getVisibleArea()` — which feed the ambient-cutout pass — track the
|
|
27756
|
+
* new size. The Canvas renderer's gradient cache auto-invalidates on
|
|
27757
|
+
* next draw via its property comparison; the WebGL procedural shader
|
|
27758
|
+
* adapts to the new dimensions automatically.
|
|
27759
|
+
*
|
|
27760
|
+
* Named `setRadii` (not `resize`) so it does not shadow
|
|
27761
|
+
* `Renderable.resize(width, height)` — code that operates on a
|
|
27762
|
+
* generic `Renderable` and calls `.resize(w, h)` keeps working when
|
|
27763
|
+
* the instance happens to be a `Light2d`.
|
|
27764
|
+
* @param {number} radiusX - new horizontal radius
|
|
27765
|
+
* @param {number} [radiusY=radiusX] - new vertical radius
|
|
27766
|
+
*/
|
|
27767
|
+
setRadii(radiusX, radiusY = radiusX) {
|
|
27768
|
+
this.radiusX = radiusX;
|
|
27769
|
+
this.radiusY = radiusY;
|
|
27770
|
+
this.resize(radiusX * 2, radiusY * 2);
|
|
27771
|
+
}
|
|
27772
|
+
/**
|
|
27773
|
+
* returns a geometry representing the visible area of this light, in
|
|
27774
|
+
* world-space coordinates (so it aligns with the rendered gradient
|
|
27775
|
+
* regardless of camera scroll or container parenting).
|
|
26908
27776
|
* @returns {Ellipse} the light visible mask
|
|
26909
27777
|
*/
|
|
26910
27778
|
getVisibleArea() {
|
|
26911
|
-
|
|
26912
|
-
|
|
26913
|
-
this.getBounds().centerY,
|
|
26914
|
-
this.width,
|
|
26915
|
-
this.height
|
|
26916
|
-
);
|
|
27779
|
+
const b = this.getBounds();
|
|
27780
|
+
return this.visibleArea.setShape(b.centerX, b.centerY, b.width, b.height);
|
|
26917
27781
|
}
|
|
26918
27782
|
/**
|
|
26919
27783
|
* update function
|
|
26920
|
-
* @param {number} dt - time since the last update in milliseconds.
|
|
26921
27784
|
* @returns {boolean} true if dirty
|
|
26922
27785
|
*/
|
|
26923
27786
|
update() {
|
|
@@ -26925,25 +27788,50 @@ var Light2d = class extends Renderable {
|
|
|
26925
27788
|
}
|
|
26926
27789
|
/**
|
|
26927
27790
|
* 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
|
|
27791
|
+
* @param {Renderer} renderer - a renderer instance
|
|
26931
27792
|
*/
|
|
26932
27793
|
preDraw(renderer2) {
|
|
26933
27794
|
super.preDraw(renderer2);
|
|
26934
27795
|
renderer2.setBlendMode(this.blendMode);
|
|
26935
27796
|
}
|
|
26936
27797
|
/**
|
|
26937
|
-
* draw this Light2d (automatically called by melonJS)
|
|
26938
|
-
*
|
|
26939
|
-
*
|
|
27798
|
+
* draw this Light2d (automatically called by melonJS).
|
|
27799
|
+
*
|
|
27800
|
+
* Delegates to `renderer.drawLight(this)` — each renderer picks its
|
|
27801
|
+
* own implementation (procedural shader on WebGL; cached `Gradient`
|
|
27802
|
+
* rasterized into a shared `CanvasRenderTarget` on Canvas). Light2d
|
|
27803
|
+
* itself doesn't know which path is used.
|
|
27804
|
+
* @param {Renderer} renderer - a renderer instance
|
|
26940
27805
|
*/
|
|
26941
27806
|
draw(renderer2) {
|
|
26942
|
-
|
|
26943
|
-
|
|
26944
|
-
|
|
26945
|
-
|
|
26946
|
-
|
|
27807
|
+
if (this.illuminationOnly) {
|
|
27808
|
+
return;
|
|
27809
|
+
}
|
|
27810
|
+
renderer2.drawLight(this);
|
|
27811
|
+
}
|
|
27812
|
+
/**
|
|
27813
|
+
* Auto-register this light with the active Stage's lighting set when
|
|
27814
|
+
* added to a container. The Stage uses that set to build the ambient
|
|
27815
|
+
* overlay cutouts; rendering the light itself is handled normally as
|
|
27816
|
+
* part of the world tree walk.
|
|
27817
|
+
* @ignore
|
|
27818
|
+
*/
|
|
27819
|
+
onActivateEvent() {
|
|
27820
|
+
const stage = state_default.current();
|
|
27821
|
+
if (stage && typeof stage._registerLight === "function") {
|
|
27822
|
+
stage._registerLight(this);
|
|
27823
|
+
}
|
|
27824
|
+
}
|
|
27825
|
+
/**
|
|
27826
|
+
* Auto-deregister this light from the active Stage's lighting set when
|
|
27827
|
+
* removed from a container.
|
|
27828
|
+
* @ignore
|
|
27829
|
+
*/
|
|
27830
|
+
onDeactivateEvent() {
|
|
27831
|
+
const stage = state_default.current();
|
|
27832
|
+
if (stage && typeof stage._unregisterLight === "function") {
|
|
27833
|
+
stage._unregisterLight(this);
|
|
27834
|
+
}
|
|
26947
27835
|
}
|
|
26948
27836
|
/**
|
|
26949
27837
|
* Destroy function<br>
|
|
@@ -26952,9 +27840,6 @@ var Light2d = class extends Renderable {
|
|
|
26952
27840
|
destroy() {
|
|
26953
27841
|
colorPool.release(this.color);
|
|
26954
27842
|
this.color = void 0;
|
|
26955
|
-
const renderer2 = this.parentApp?.renderer ?? game.renderer;
|
|
26956
|
-
this.texture.destroy(renderer2);
|
|
26957
|
-
this.texture = void 0;
|
|
26958
27843
|
ellipsePool.release(this.visibleArea);
|
|
26959
27844
|
this.visibleArea = void 0;
|
|
26960
27845
|
super.destroy();
|
|
@@ -27406,7 +28291,7 @@ var Trigger = class extends Renderable {
|
|
|
27406
28291
|
};
|
|
27407
28292
|
|
|
27408
28293
|
// src/version.ts
|
|
27409
|
-
var version = "19.
|
|
28294
|
+
var version = "19.4.0";
|
|
27410
28295
|
|
|
27411
28296
|
// src/system/bootstrap.ts
|
|
27412
28297
|
var initialized = false;
|
|
@@ -28267,10 +29152,7 @@ function enablePointerEvent() {
|
|
|
28267
29152
|
if (activeEventList.indexOf(events[i]) !== -1) {
|
|
28268
29153
|
pointerEventTarget.addEventListener(
|
|
28269
29154
|
events[i],
|
|
28270
|
-
throttle(
|
|
28271
|
-
onMoveEvent,
|
|
28272
|
-
throttlingInterval
|
|
28273
|
-
),
|
|
29155
|
+
throttle(onMoveEvent, throttlingInterval),
|
|
28274
29156
|
{ passive: true }
|
|
28275
29157
|
// do not preventDefault on Move events
|
|
28276
29158
|
);
|
|
@@ -28783,7 +29665,7 @@ function testEllipseEllipse(a, ellipseA, b, ellipseB, response) {
|
|
|
28783
29665
|
return testEllipsePolygon(a, ellipseA, b, ellipseB.toPolygon(), response);
|
|
28784
29666
|
}
|
|
28785
29667
|
}
|
|
28786
|
-
const differenceV = T_VECTORS[--T_VECTORS_IDX].copy(b.pos).add(b.ancestor.getAbsolutePosition()).add(ellipseB.pos).sub(a.pos).
|
|
29668
|
+
const differenceV = T_VECTORS[--T_VECTORS_IDX].copy(b.pos).add(b.ancestor.getAbsolutePosition()).add(ellipseB.pos).sub(a.pos).sub(a.ancestor.getAbsolutePosition()).sub(ellipseA.pos);
|
|
28787
29669
|
const radiusA = ellipseA.radius;
|
|
28788
29670
|
const radiusB = ellipseB.radius;
|
|
28789
29671
|
const totalRadius = radiusA + radiusB;
|
|
@@ -28810,7 +29692,7 @@ function testPolygonEllipse(a, polyA, b, ellipseB, response) {
|
|
|
28810
29692
|
if (ellipseB.radiusV.x !== ellipseB.radiusV.y) {
|
|
28811
29693
|
return testPolygonPolygon(a, polyA, b, ellipseB.toPolygon(), response);
|
|
28812
29694
|
}
|
|
28813
|
-
const circlePos = T_VECTORS[--T_VECTORS_IDX].copy(b.pos).add(b.ancestor.getAbsolutePosition()).add(ellipseB.pos).sub(a.pos).
|
|
29695
|
+
const circlePos = T_VECTORS[--T_VECTORS_IDX].copy(b.pos).add(b.ancestor.getAbsolutePosition()).add(ellipseB.pos).sub(a.pos).sub(a.ancestor.getAbsolutePosition()).sub(polyA.pos);
|
|
28814
29696
|
const radius = ellipseB.radius;
|
|
28815
29697
|
const radius2 = radius * radius;
|
|
28816
29698
|
const points = polyA.points;
|
|
@@ -29436,6 +30318,7 @@ var World = class extends Container {
|
|
|
29436
30318
|
this.fps = 60;
|
|
29437
30319
|
this.gravity = new Vector2d(0, 0.98);
|
|
29438
30320
|
this.preRender = false;
|
|
30321
|
+
this.gpuTilemap = true;
|
|
29439
30322
|
this.bodies = /* @__PURE__ */ new Set();
|
|
29440
30323
|
this.broadphase = new QuadTree(
|
|
29441
30324
|
this,
|
|
@@ -30008,114 +30891,92 @@ function generateJoinCircles(centers, radius) {
|
|
|
30008
30891
|
return verts;
|
|
30009
30892
|
}
|
|
30010
30893
|
|
|
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";
|
|
30894
|
+
// src/video/webgl/lighting/constants.ts
|
|
30895
|
+
var MAX_LIGHTS = 8;
|
|
30016
30896
|
|
|
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);
|
|
30897
|
+
// src/video/webgl/shaders/multitexture-lit.js
|
|
30898
|
+
function buildSamplerSelect(varName, samplerPrefix, count, target) {
|
|
30899
|
+
const lines = [];
|
|
30900
|
+
for (let i = 0; i < count; i++) {
|
|
30901
|
+
if (i === 0) {
|
|
30902
|
+
lines.push(" if (" + varName + " < 0.5) {");
|
|
30103
30903
|
} else {
|
|
30104
|
-
|
|
30904
|
+
lines.push(" } else if (" + varName + " < " + (i + 0.5) + ") {");
|
|
30105
30905
|
}
|
|
30906
|
+
lines.push(
|
|
30907
|
+
" " + target + " = texture2D(" + samplerPrefix + i + ", vRegion);"
|
|
30908
|
+
);
|
|
30106
30909
|
}
|
|
30107
|
-
|
|
30108
|
-
|
|
30109
|
-
|
|
30110
|
-
|
|
30111
|
-
|
|
30112
|
-
|
|
30113
|
-
|
|
30114
|
-
|
|
30115
|
-
|
|
30116
|
-
|
|
30910
|
+
lines.push(" } else {");
|
|
30911
|
+
lines.push(
|
|
30912
|
+
" " + target + " = texture2D(" + samplerPrefix + "0, vRegion);"
|
|
30913
|
+
);
|
|
30914
|
+
lines.push(" }");
|
|
30915
|
+
return lines;
|
|
30916
|
+
}
|
|
30917
|
+
function buildLitMultiTextureFragment(maxTextures) {
|
|
30918
|
+
const count = Math.max(maxTextures, 1);
|
|
30919
|
+
const lines = [];
|
|
30920
|
+
for (let i = 0; i < count; i++) {
|
|
30921
|
+
lines.push("uniform sampler2D uSampler" + i + ";");
|
|
30117
30922
|
}
|
|
30118
|
-
|
|
30923
|
+
for (let i = 0; i < count; i++) {
|
|
30924
|
+
lines.push("uniform sampler2D uNormalSampler" + i + ";");
|
|
30925
|
+
}
|
|
30926
|
+
lines.push("uniform int uLightCount;");
|
|
30927
|
+
lines.push("uniform vec4 uLightPos[" + MAX_LIGHTS + "];");
|
|
30928
|
+
lines.push("uniform vec3 uLightColor[" + MAX_LIGHTS + "];");
|
|
30929
|
+
lines.push("uniform float uLightHeight[" + MAX_LIGHTS + "];");
|
|
30930
|
+
lines.push("uniform vec3 uAmbient;");
|
|
30931
|
+
lines.push("varying vec4 vColor;");
|
|
30932
|
+
lines.push("varying vec2 vRegion;");
|
|
30933
|
+
lines.push("varying float vTextureId;");
|
|
30934
|
+
lines.push("varying float vNormalTextureId;");
|
|
30935
|
+
lines.push("varying vec2 vWorldPos;");
|
|
30936
|
+
lines.push("");
|
|
30937
|
+
lines.push("void main(void) {");
|
|
30938
|
+
lines.push(" vec4 color;");
|
|
30939
|
+
lines.push(...buildSamplerSelect("vTextureId", "uSampler", count, "color"));
|
|
30940
|
+
lines.push(" if (vNormalTextureId < -0.5) {");
|
|
30941
|
+
lines.push(" gl_FragColor = color * vColor;");
|
|
30942
|
+
lines.push(" return;");
|
|
30943
|
+
lines.push(" }");
|
|
30944
|
+
lines.push(" vec4 normalSample;");
|
|
30945
|
+
lines.push(
|
|
30946
|
+
...buildSamplerSelect(
|
|
30947
|
+
"vNormalTextureId",
|
|
30948
|
+
"uNormalSampler",
|
|
30949
|
+
count,
|
|
30950
|
+
"normalSample"
|
|
30951
|
+
)
|
|
30952
|
+
);
|
|
30953
|
+
lines.push(
|
|
30954
|
+
" vec3 normal = normalize(normalSample.rgb * 2.0 - vec3(1.0));"
|
|
30955
|
+
);
|
|
30956
|
+
lines.push(" normal.y = -normal.y;");
|
|
30957
|
+
lines.push(" vec3 lighting = uAmbient;");
|
|
30958
|
+
lines.push(" for (int i = 0; i < " + MAX_LIGHTS + "; i++) {");
|
|
30959
|
+
lines.push(" if (i >= uLightCount) break;");
|
|
30960
|
+
lines.push(" vec4 lp = uLightPos[i];");
|
|
30961
|
+
lines.push(" vec2 toLight = lp.xy - vWorldPos;");
|
|
30962
|
+
lines.push(" float dist = length(toLight);");
|
|
30963
|
+
lines.push(" float linear = max(0.0, 1.0 - dist / max(lp.z, 1.0));");
|
|
30964
|
+
lines.push(" float att = linear * linear;");
|
|
30965
|
+
lines.push(
|
|
30966
|
+
" vec3 lightDir = normalize(vec3(toLight, uLightHeight[i]));"
|
|
30967
|
+
);
|
|
30968
|
+
lines.push(" float NdotL = max(0.0, dot(normal, lightDir));");
|
|
30969
|
+
lines.push(" lighting += uLightColor[i] * (lp.w * att * NdotL);");
|
|
30970
|
+
lines.push(" }");
|
|
30971
|
+
lines.push(
|
|
30972
|
+
" gl_FragColor = vec4(color.rgb * lighting, color.a) * vColor;"
|
|
30973
|
+
);
|
|
30974
|
+
lines.push("}");
|
|
30975
|
+
return lines.join("\n");
|
|
30976
|
+
}
|
|
30977
|
+
|
|
30978
|
+
// src/video/webgl/shaders/quad-multi-lit.vert
|
|
30979
|
+
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
30980
|
|
|
30120
30981
|
// src/video/buffer/index.js
|
|
30121
30982
|
var IndexBuffer = class {
|
|
@@ -30216,6 +31077,145 @@ var WebGLIndexBuffer = class extends IndexBuffer {
|
|
|
30216
31077
|
}
|
|
30217
31078
|
};
|
|
30218
31079
|
|
|
31080
|
+
// src/video/webgl/shaders/multitexture.js
|
|
31081
|
+
function buildMultiTextureFragment(maxTextures) {
|
|
31082
|
+
const count = Math.max(maxTextures, 1);
|
|
31083
|
+
const lines = [];
|
|
31084
|
+
for (let i = 0; i < count; i++) {
|
|
31085
|
+
lines.push("uniform sampler2D uSampler" + i + ";");
|
|
31086
|
+
}
|
|
31087
|
+
lines.push("varying vec4 vColor;");
|
|
31088
|
+
lines.push("varying vec2 vRegion;");
|
|
31089
|
+
lines.push("varying float vTextureId;");
|
|
31090
|
+
lines.push("");
|
|
31091
|
+
lines.push("void main(void) {");
|
|
31092
|
+
lines.push(" vec4 color;");
|
|
31093
|
+
for (let i = 0; i < count; i++) {
|
|
31094
|
+
if (i === 0) {
|
|
31095
|
+
lines.push(" if (vTextureId < 0.5) {");
|
|
31096
|
+
} else {
|
|
31097
|
+
lines.push(" } else if (vTextureId < " + (i + 0.5) + ") {");
|
|
31098
|
+
}
|
|
31099
|
+
lines.push(" color = texture2D(uSampler" + i + ", vRegion);");
|
|
31100
|
+
}
|
|
31101
|
+
lines.push(" } else {");
|
|
31102
|
+
lines.push(" color = texture2D(uSampler0, vRegion);");
|
|
31103
|
+
lines.push(" }");
|
|
31104
|
+
lines.push(" gl_FragColor = color * vColor;");
|
|
31105
|
+
lines.push("}");
|
|
31106
|
+
return lines.join("\n");
|
|
31107
|
+
}
|
|
31108
|
+
|
|
31109
|
+
// src/video/webgl/shaders/quad-multi.vert
|
|
31110
|
+
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";
|
|
31111
|
+
|
|
31112
|
+
// src/video/buffer/vertex.js
|
|
31113
|
+
var VertexArrayBuffer = class {
|
|
31114
|
+
constructor(vertexSize, maxVertex) {
|
|
31115
|
+
this.vertexSize = vertexSize;
|
|
31116
|
+
this.maxVertex = maxVertex;
|
|
31117
|
+
this.vertexCount = 0;
|
|
31118
|
+
this.buffer = new ArrayBuffer(
|
|
31119
|
+
this.maxVertex * this.vertexSize * Float32Array.BYTES_PER_ELEMENT
|
|
31120
|
+
);
|
|
31121
|
+
this.bufferF32 = new Float32Array(this.buffer);
|
|
31122
|
+
this.bufferU32 = new Uint32Array(this.buffer);
|
|
31123
|
+
}
|
|
31124
|
+
/**
|
|
31125
|
+
* clear the vertex array buffer
|
|
31126
|
+
* @ignore
|
|
31127
|
+
*/
|
|
31128
|
+
clear() {
|
|
31129
|
+
this.vertexCount = 0;
|
|
31130
|
+
}
|
|
31131
|
+
/**
|
|
31132
|
+
* return true if full
|
|
31133
|
+
* @ignore
|
|
31134
|
+
*/
|
|
31135
|
+
isFull(vertex) {
|
|
31136
|
+
return this.vertexCount + vertex >= this.maxVertex;
|
|
31137
|
+
}
|
|
31138
|
+
/**
|
|
31139
|
+
* push a new vertex to the buffer
|
|
31140
|
+
* @param {number} x - x position
|
|
31141
|
+
* @param {number} y - y position
|
|
31142
|
+
* @param {number} u - texture U coordinate
|
|
31143
|
+
* @param {number} v - texture V coordinate
|
|
31144
|
+
* @param {number} tint - tint color in UINT32 (argb) format
|
|
31145
|
+
* @param {number} [textureId] - texture unit index for multi-texture batching
|
|
31146
|
+
* @param {number} [normalTextureId] - paired normal-map texture unit index, or `-1` for unlit quads
|
|
31147
|
+
* @ignore
|
|
31148
|
+
*/
|
|
31149
|
+
push(x, y, u, v, tint, textureId, normalTextureId) {
|
|
31150
|
+
const offset = this.vertexCount * this.vertexSize;
|
|
31151
|
+
this.bufferF32[offset] = x;
|
|
31152
|
+
this.bufferF32[offset + 1] = y;
|
|
31153
|
+
this.bufferF32[offset + 2] = u;
|
|
31154
|
+
this.bufferF32[offset + 3] = v;
|
|
31155
|
+
this.bufferU32[offset + 4] = tint;
|
|
31156
|
+
if (this.vertexSize > 5) {
|
|
31157
|
+
this.bufferF32[offset + 5] = textureId || 0;
|
|
31158
|
+
if (this.vertexSize > 6) {
|
|
31159
|
+
this.bufferF32[offset + 6] = typeof normalTextureId === "number" ? normalTextureId : -1;
|
|
31160
|
+
}
|
|
31161
|
+
}
|
|
31162
|
+
this.vertexCount++;
|
|
31163
|
+
return this;
|
|
31164
|
+
}
|
|
31165
|
+
/**
|
|
31166
|
+
* push a new vertex with all-float data to the buffer
|
|
31167
|
+
* @param {ArrayLike<number>} data - float values for one vertex
|
|
31168
|
+
* @param {number} srcOffset - start index in the source data
|
|
31169
|
+
* @param {number} count - number of floats to copy (should equal vertexSize)
|
|
31170
|
+
* @ignore
|
|
31171
|
+
*/
|
|
31172
|
+
pushFloats(data2, srcOffset, count) {
|
|
31173
|
+
const offset = this.vertexCount * this.vertexSize;
|
|
31174
|
+
for (let i = 0; i < count; i++) {
|
|
31175
|
+
this.bufferF32[offset + i] = data2[srcOffset + i];
|
|
31176
|
+
}
|
|
31177
|
+
this.vertexCount++;
|
|
31178
|
+
return this;
|
|
31179
|
+
}
|
|
31180
|
+
/**
|
|
31181
|
+
* push a new vertex to the buffer (mesh format: x, y, z, u, v, tint)
|
|
31182
|
+
* @ignore
|
|
31183
|
+
*/
|
|
31184
|
+
pushMesh(x, y, z, u, v, tint) {
|
|
31185
|
+
const offset = this.vertexCount * this.vertexSize;
|
|
31186
|
+
this.bufferF32[offset] = x;
|
|
31187
|
+
this.bufferF32[offset + 1] = y;
|
|
31188
|
+
this.bufferF32[offset + 2] = z;
|
|
31189
|
+
this.bufferF32[offset + 3] = u;
|
|
31190
|
+
this.bufferF32[offset + 4] = v;
|
|
31191
|
+
this.bufferU32[offset + 5] = tint;
|
|
31192
|
+
this.vertexCount++;
|
|
31193
|
+
return this;
|
|
31194
|
+
}
|
|
31195
|
+
/**
|
|
31196
|
+
* return a reference to the data in Float32 format
|
|
31197
|
+
* @ignore
|
|
31198
|
+
*/
|
|
31199
|
+
toFloat32(begin, end) {
|
|
31200
|
+
if (typeof end !== "undefined") {
|
|
31201
|
+
return this.bufferF32.subarray(begin, end);
|
|
31202
|
+
} else {
|
|
31203
|
+
return this.bufferF32;
|
|
31204
|
+
}
|
|
31205
|
+
}
|
|
31206
|
+
/**
|
|
31207
|
+
* return a reference to the data in Uint32 format
|
|
31208
|
+
* @ignore
|
|
31209
|
+
*/
|
|
31210
|
+
toUint32(begin, end) {
|
|
31211
|
+
if (typeof end !== "undefined") {
|
|
31212
|
+
return this.bufferU32.subarray(begin, end);
|
|
31213
|
+
} else {
|
|
31214
|
+
return this.bufferU32;
|
|
31215
|
+
}
|
|
31216
|
+
}
|
|
31217
|
+
};
|
|
31218
|
+
|
|
30219
31219
|
// src/video/webgl/batchers/batcher.js
|
|
30220
31220
|
var DEFAULT_MAX_VERTICES = 4096;
|
|
30221
31221
|
var Batcher = class {
|
|
@@ -30314,6 +31314,25 @@ var Batcher = class {
|
|
|
30314
31314
|
this.useShader(this.defaultShader);
|
|
30315
31315
|
}
|
|
30316
31316
|
}
|
|
31317
|
+
/**
|
|
31318
|
+
* called by the WebGL renderer when this batcher is being replaced by another.
|
|
31319
|
+
* Disables this batcher's vertex attribute locations so they don't leak across
|
|
31320
|
+
* (otherwise stale stride/offset state can cause INVALID_OPERATION on the next draw).
|
|
31321
|
+
*/
|
|
31322
|
+
unbind() {
|
|
31323
|
+
if (this.currentShader === void 0) {
|
|
31324
|
+
return;
|
|
31325
|
+
}
|
|
31326
|
+
const gl = this.gl;
|
|
31327
|
+
for (let i = 0; i < this.attributes.length; ++i) {
|
|
31328
|
+
const location = this.currentShader.getAttribLocation(
|
|
31329
|
+
this.attributes[i].name
|
|
31330
|
+
);
|
|
31331
|
+
if (location !== -1) {
|
|
31332
|
+
gl.disableVertexAttribArray(location);
|
|
31333
|
+
}
|
|
31334
|
+
}
|
|
31335
|
+
}
|
|
30317
31336
|
/**
|
|
30318
31337
|
* Select the shader to use for compositing
|
|
30319
31338
|
* @see GLShader
|
|
@@ -30322,6 +31341,9 @@ var Batcher = class {
|
|
|
30322
31341
|
useShader(shader) {
|
|
30323
31342
|
if (this.currentShader !== shader || this.renderer.currentProgram !== shader.program) {
|
|
30324
31343
|
this.flush();
|
|
31344
|
+
if (this.currentShader && this.currentShader !== shader) {
|
|
31345
|
+
this.unbind();
|
|
31346
|
+
}
|
|
30325
31347
|
shader.bind();
|
|
30326
31348
|
shader.setUniform(this.projectionUniform, this.renderer.projectionMatrix);
|
|
30327
31349
|
shader.setVertexAttributes(this.gl, this.attributes, this.stride);
|
|
@@ -30492,8 +31514,15 @@ var MaterialBatcher = class extends Batcher {
|
|
|
30492
31514
|
createTexture2D(unit, pixels = null, filter, repeat = "no-repeat", w = pixels.width, h = pixels.height, premultipliedAlpha = true, mipmap = true, texture, flush = true) {
|
|
30493
31515
|
const gl = this.gl;
|
|
30494
31516
|
const isPOT = isPowerOfTwo(w) && isPowerOfTwo(h);
|
|
30495
|
-
const
|
|
30496
|
-
const
|
|
31517
|
+
const wantsRepeat = repeat !== "no-repeat";
|
|
31518
|
+
const canRepeat = isPOT || this.renderer.WebGLVersion > 1;
|
|
31519
|
+
const rs = repeat.search(/^repeat(-x)?$/) === 0 && canRepeat ? gl.REPEAT : gl.CLAMP_TO_EDGE;
|
|
31520
|
+
const rt = repeat.search(/^repeat(-y)?$/) === 0 && canRepeat ? gl.REPEAT : gl.CLAMP_TO_EDGE;
|
|
31521
|
+
if (wantsRepeat && !canRepeat) {
|
|
31522
|
+
console.warn(
|
|
31523
|
+
"melonJS: repeat wrap (" + repeat + ") requested on a non-power-of-two texture (" + w + "x" + h + ") under WebGL 1 \u2014 downgrading to clamp-to-edge"
|
|
31524
|
+
);
|
|
31525
|
+
}
|
|
30497
31526
|
let currentTexture = texture;
|
|
30498
31527
|
if (!currentTexture) {
|
|
30499
31528
|
currentTexture = gl.createTexture();
|
|
@@ -30504,7 +31533,9 @@ var MaterialBatcher = class extends Batcher {
|
|
|
30504
31533
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
|
|
30505
31534
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter);
|
|
30506
31535
|
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultipliedAlpha);
|
|
30507
|
-
if (pixels !== null && pixels.
|
|
31536
|
+
if (pixels !== null && typeof pixels.upload === "function") {
|
|
31537
|
+
pixels.upload(gl, gl.TEXTURE_2D);
|
|
31538
|
+
} else if (pixels !== null && pixels.compressed === true) {
|
|
30508
31539
|
const mipmaps = pixels.mipmaps;
|
|
30509
31540
|
for (let i = 0; i < mipmaps.length; i++) {
|
|
30510
31541
|
gl.compressedTexImage2D(
|
|
@@ -30565,7 +31596,9 @@ var MaterialBatcher = class extends Batcher {
|
|
|
30565
31596
|
pixels
|
|
30566
31597
|
);
|
|
30567
31598
|
}
|
|
30568
|
-
if (isPOT && mipmap === true &&
|
|
31599
|
+
if (isPOT && mipmap === true && pixels !== null && pixels.compressed !== true && typeof pixels.upload !== "function") {
|
|
31600
|
+
gl.generateMipmap(gl.TEXTURE_2D);
|
|
31601
|
+
} else if (pixels === null && isPOT && mipmap === true) {
|
|
30569
31602
|
gl.generateMipmap(gl.TEXTURE_2D);
|
|
30570
31603
|
}
|
|
30571
31604
|
return currentTexture;
|
|
@@ -30643,18 +31676,29 @@ var MaterialBatcher = class extends Batcher {
|
|
|
30643
31676
|
}
|
|
30644
31677
|
/**
|
|
30645
31678
|
* @ignore
|
|
31679
|
+
* @param {TextureAtlas|TextureResource} texture
|
|
31680
|
+
* @param {number} [w] - ignored when the source has its own `width` (the
|
|
31681
|
+
* common case); kept for the legacy signature where callers passed a
|
|
31682
|
+
* destination size. Forwarded only as a last-resort default.
|
|
31683
|
+
* @param {number} [h] - same as `w`.
|
|
31684
|
+
* @param {boolean} [force=false]
|
|
31685
|
+
* @param {boolean} [flush=true]
|
|
30646
31686
|
*/
|
|
30647
31687
|
uploadTexture(texture, w, h, force = false, flush = true) {
|
|
30648
31688
|
const unit = this.renderer.cache.getUnit(texture);
|
|
30649
31689
|
const texture2D = this.boundTextures[unit];
|
|
30650
31690
|
if (typeof texture2D === "undefined" || force) {
|
|
31691
|
+
const filter = typeof texture.filter !== "undefined" ? texture.filter : this.renderer.settings.antiAlias ? this.gl.LINEAR : this.gl.NEAREST;
|
|
31692
|
+
const source = texture.getTexture();
|
|
31693
|
+
const texW = source.width || source.videoWidth || w;
|
|
31694
|
+
const texH = source.height || source.videoHeight || h;
|
|
30651
31695
|
this.createTexture2D(
|
|
30652
31696
|
unit,
|
|
30653
|
-
|
|
30654
|
-
|
|
31697
|
+
source,
|
|
31698
|
+
filter,
|
|
30655
31699
|
texture.repeat,
|
|
30656
|
-
|
|
30657
|
-
|
|
31700
|
+
texW,
|
|
31701
|
+
texH,
|
|
30658
31702
|
texture.premultipliedAlpha,
|
|
30659
31703
|
void 0,
|
|
30660
31704
|
texture2D,
|
|
@@ -30667,6 +31711,514 @@ var MaterialBatcher = class extends Batcher {
|
|
|
30667
31711
|
}
|
|
30668
31712
|
};
|
|
30669
31713
|
|
|
31714
|
+
// src/video/webgl/batchers/quad_batcher.js
|
|
31715
|
+
var V_ARRAY = [
|
|
31716
|
+
new Vector2d(),
|
|
31717
|
+
new Vector2d(),
|
|
31718
|
+
new Vector2d(),
|
|
31719
|
+
new Vector2d()
|
|
31720
|
+
];
|
|
31721
|
+
var QuadBatcher = class extends MaterialBatcher {
|
|
31722
|
+
/**
|
|
31723
|
+
* Initialize the compositor
|
|
31724
|
+
* @ignore
|
|
31725
|
+
*/
|
|
31726
|
+
init(renderer2) {
|
|
31727
|
+
this.maxBatchTextures = Math.min(renderer2.maxTextures, 16);
|
|
31728
|
+
super.init(renderer2, {
|
|
31729
|
+
attributes: [
|
|
31730
|
+
{
|
|
31731
|
+
name: "aVertex",
|
|
31732
|
+
size: 2,
|
|
31733
|
+
type: renderer2.gl.FLOAT,
|
|
31734
|
+
normalized: false,
|
|
31735
|
+
offset: 0 * Float32Array.BYTES_PER_ELEMENT
|
|
31736
|
+
},
|
|
31737
|
+
{
|
|
31738
|
+
name: "aRegion",
|
|
31739
|
+
size: 2,
|
|
31740
|
+
type: renderer2.gl.FLOAT,
|
|
31741
|
+
normalized: false,
|
|
31742
|
+
offset: 2 * Float32Array.BYTES_PER_ELEMENT
|
|
31743
|
+
},
|
|
31744
|
+
{
|
|
31745
|
+
name: "aColor",
|
|
31746
|
+
size: 4,
|
|
31747
|
+
type: renderer2.gl.UNSIGNED_BYTE,
|
|
31748
|
+
normalized: true,
|
|
31749
|
+
offset: 4 * Float32Array.BYTES_PER_ELEMENT
|
|
31750
|
+
},
|
|
31751
|
+
{
|
|
31752
|
+
name: "aTextureId",
|
|
31753
|
+
size: 1,
|
|
31754
|
+
type: renderer2.gl.FLOAT,
|
|
31755
|
+
normalized: false,
|
|
31756
|
+
offset: 5 * Float32Array.BYTES_PER_ELEMENT
|
|
31757
|
+
}
|
|
31758
|
+
],
|
|
31759
|
+
shader: {
|
|
31760
|
+
vertex: quad_multi_default,
|
|
31761
|
+
fragment: buildMultiTextureFragment(this.maxBatchTextures)
|
|
31762
|
+
}
|
|
31763
|
+
});
|
|
31764
|
+
this.bindColorSamplers();
|
|
31765
|
+
this.useMultiTexture = true;
|
|
31766
|
+
this.createIndexBuffer();
|
|
31767
|
+
}
|
|
31768
|
+
/**
|
|
31769
|
+
* (Re-)create the index buffer for quad batching (4 verts + 6 indices per quad).
|
|
31770
|
+
* Called from `init` and `reset` (after context loss).
|
|
31771
|
+
* @ignore
|
|
31772
|
+
*/
|
|
31773
|
+
createIndexBuffer() {
|
|
31774
|
+
const maxQuads = this.vertexData.maxVertex / 4;
|
|
31775
|
+
this.indexBuffer = new WebGLIndexBuffer(
|
|
31776
|
+
this.gl,
|
|
31777
|
+
maxQuads * 6,
|
|
31778
|
+
this.renderer.WebGLVersion > 1
|
|
31779
|
+
);
|
|
31780
|
+
this.indexBuffer.fillQuadPattern(maxQuads);
|
|
31781
|
+
}
|
|
31782
|
+
/**
|
|
31783
|
+
* Bind the color sampler uniforms (`uSampler0..uSamplerN-1`) to their
|
|
31784
|
+
* respective texture units. Called from `init` and `reset`.
|
|
31785
|
+
* @ignore
|
|
31786
|
+
*/
|
|
31787
|
+
bindColorSamplers() {
|
|
31788
|
+
for (let i = 0; i < this.maxBatchTextures; i++) {
|
|
31789
|
+
this.defaultShader.setUniform("uSampler" + i, i);
|
|
31790
|
+
}
|
|
31791
|
+
}
|
|
31792
|
+
/**
|
|
31793
|
+
* Select the shader to use for compositing.
|
|
31794
|
+
* Multi-texture batching is automatically enabled when the default
|
|
31795
|
+
* shader is active, and disabled for custom ShaderEffect shaders.
|
|
31796
|
+
* @see GLShader
|
|
31797
|
+
* @see ShaderEffect
|
|
31798
|
+
* @param {GLShader|ShaderEffect} shader - a reference to a GLShader or ShaderEffect instance
|
|
31799
|
+
*/
|
|
31800
|
+
useShader(shader) {
|
|
31801
|
+
super.useShader(shader);
|
|
31802
|
+
this.useMultiTexture = shader === this.defaultShader;
|
|
31803
|
+
}
|
|
31804
|
+
/**
|
|
31805
|
+
* Reset compositor internal state
|
|
31806
|
+
* @ignore
|
|
31807
|
+
*/
|
|
31808
|
+
reset() {
|
|
31809
|
+
super.reset();
|
|
31810
|
+
this.createIndexBuffer();
|
|
31811
|
+
this.bindColorSamplers();
|
|
31812
|
+
this.useMultiTexture = true;
|
|
31813
|
+
}
|
|
31814
|
+
/**
|
|
31815
|
+
* Flush batched texture data to the GPU using indexed drawing.
|
|
31816
|
+
* @param {number} [mode=gl.TRIANGLES] - the GL drawing mode
|
|
31817
|
+
*/
|
|
31818
|
+
flush(mode = this.mode) {
|
|
31819
|
+
const vertex = this.vertexData;
|
|
31820
|
+
const vertexCount = vertex.vertexCount;
|
|
31821
|
+
if (vertexCount > 0) {
|
|
31822
|
+
const gl = this.gl;
|
|
31823
|
+
const vertexSize = vertex.vertexSize;
|
|
31824
|
+
this.indexBuffer.bind();
|
|
31825
|
+
if (this.renderer.WebGLVersion > 1) {
|
|
31826
|
+
gl.bufferData(
|
|
31827
|
+
gl.ARRAY_BUFFER,
|
|
31828
|
+
vertex.toFloat32(),
|
|
31829
|
+
gl.STREAM_DRAW,
|
|
31830
|
+
0,
|
|
31831
|
+
vertexCount * vertexSize
|
|
31832
|
+
);
|
|
31833
|
+
} else {
|
|
31834
|
+
gl.bufferData(
|
|
31835
|
+
gl.ARRAY_BUFFER,
|
|
31836
|
+
vertex.toFloat32(0, vertexCount * vertexSize),
|
|
31837
|
+
gl.STREAM_DRAW
|
|
31838
|
+
);
|
|
31839
|
+
}
|
|
31840
|
+
const indexCount = vertexCount / 4 * 6;
|
|
31841
|
+
gl.drawElements(mode, indexCount, this.indexBuffer.type, 0);
|
|
31842
|
+
vertex.clear();
|
|
31843
|
+
}
|
|
31844
|
+
}
|
|
31845
|
+
/**
|
|
31846
|
+
* Draw a screen-aligned quad with the given raw WebGL texture through the given shader.
|
|
31847
|
+
* Binds the texture to unit 0, pushes 4 vertices (Y-flipped UVs), flushes,
|
|
31848
|
+
* then unbinds the texture.
|
|
31849
|
+
* @param {WebGLTexture} source - the raw GL texture to blit
|
|
31850
|
+
* @param {number} x - destination x
|
|
31851
|
+
* @param {number} y - destination y
|
|
31852
|
+
* @param {number} width - destination width
|
|
31853
|
+
* @param {number} height - destination height
|
|
31854
|
+
* @param {GLShader|ShaderEffect} shader - the shader effect to apply
|
|
31855
|
+
*/
|
|
31856
|
+
blitTexture(source, x, y, width, height, shader) {
|
|
31857
|
+
const gl = this.gl;
|
|
31858
|
+
this.useShader(shader);
|
|
31859
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
31860
|
+
gl.bindTexture(gl.TEXTURE_2D, source);
|
|
31861
|
+
this.currentTextureUnit = 0;
|
|
31862
|
+
this.boundTextures[0] = source;
|
|
31863
|
+
shader.setUniform("uSampler", 0);
|
|
31864
|
+
const m = this.viewMatrix;
|
|
31865
|
+
const vec0 = V_ARRAY[0].set(x, y);
|
|
31866
|
+
const vec1 = V_ARRAY[1].set(x + width, y);
|
|
31867
|
+
const vec2 = V_ARRAY[2].set(x, y + height);
|
|
31868
|
+
const vec3 = V_ARRAY[3].set(x + width, y + height);
|
|
31869
|
+
if (m && !m.isIdentity()) {
|
|
31870
|
+
m.apply(vec0);
|
|
31871
|
+
m.apply(vec1);
|
|
31872
|
+
m.apply(vec2);
|
|
31873
|
+
m.apply(vec3);
|
|
31874
|
+
}
|
|
31875
|
+
const tint = 4294967295;
|
|
31876
|
+
this.vertexData.push(vec0.x, vec0.y, 0, 1, tint, 0);
|
|
31877
|
+
this.vertexData.push(vec1.x, vec1.y, 1, 1, tint, 0);
|
|
31878
|
+
this.vertexData.push(vec2.x, vec2.y, 0, 0, tint, 0);
|
|
31879
|
+
this.vertexData.push(vec3.x, vec3.y, 1, 0, tint, 0);
|
|
31880
|
+
this.flush();
|
|
31881
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
31882
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
31883
|
+
this.currentTextureUnit = -1;
|
|
31884
|
+
delete this.boundTextures[0];
|
|
31885
|
+
this.useShader(this.defaultShader);
|
|
31886
|
+
}
|
|
31887
|
+
/**
|
|
31888
|
+
* Add a textured quad
|
|
31889
|
+
* @param {TextureAtlas} texture - Source texture atlas
|
|
31890
|
+
* @param {number} x - Destination x-coordinate
|
|
31891
|
+
* @param {number} y - Destination y-coordinate
|
|
31892
|
+
* @param {number} w - Destination width
|
|
31893
|
+
* @param {number} h - Destination height
|
|
31894
|
+
* @param {number} u0 - Texture UV (u0) value.
|
|
31895
|
+
* @param {number} v0 - Texture UV (v0) value.
|
|
31896
|
+
* @param {number} u1 - Texture UV (u1) value.
|
|
31897
|
+
* @param {number} v1 - Texture UV (v1) value.
|
|
31898
|
+
* @param {number} tint - tint color to be applied to the texture in UINT32 (argb) format
|
|
31899
|
+
* @param {boolean} [reupload=false] - Force the texture to be reuploaded even if already bound
|
|
31900
|
+
*/
|
|
31901
|
+
addQuad(texture, x, y, w, h, u0, v0, u1, v1, tint, reupload = false) {
|
|
31902
|
+
const vertexData = this.vertexData;
|
|
31903
|
+
if (vertexData.isFull(4)) {
|
|
31904
|
+
this.flush();
|
|
31905
|
+
}
|
|
31906
|
+
let unit;
|
|
31907
|
+
if (this.useMultiTexture) {
|
|
31908
|
+
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
31909
|
+
if (unit >= this.maxBatchTextures) {
|
|
31910
|
+
this.flush();
|
|
31911
|
+
this.renderer.cache.resetUnitAssignments();
|
|
31912
|
+
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
31913
|
+
}
|
|
31914
|
+
} else {
|
|
31915
|
+
unit = this.uploadTexture(texture, w, h, reupload);
|
|
31916
|
+
if (unit !== this.currentSamplerUnit) {
|
|
31917
|
+
this.currentShader.setUniform("uSampler", unit);
|
|
31918
|
+
this.currentSamplerUnit = unit;
|
|
31919
|
+
}
|
|
31920
|
+
}
|
|
31921
|
+
const m = this.viewMatrix;
|
|
31922
|
+
const vec0 = V_ARRAY[0].set(x, y);
|
|
31923
|
+
const vec1 = V_ARRAY[1].set(x + w, y);
|
|
31924
|
+
const vec2 = V_ARRAY[2].set(x, y + h);
|
|
31925
|
+
const vec3 = V_ARRAY[3].set(x + w, y + h);
|
|
31926
|
+
if (!m.isIdentity()) {
|
|
31927
|
+
m.apply(vec0);
|
|
31928
|
+
m.apply(vec1);
|
|
31929
|
+
m.apply(vec2);
|
|
31930
|
+
m.apply(vec3);
|
|
31931
|
+
}
|
|
31932
|
+
const textureId = this.useMultiTexture ? unit : 0;
|
|
31933
|
+
vertexData.push(vec0.x, vec0.y, u0, v0, tint, textureId);
|
|
31934
|
+
vertexData.push(vec1.x, vec1.y, u1, v0, tint, textureId);
|
|
31935
|
+
vertexData.push(vec2.x, vec2.y, u0, v1, tint, textureId);
|
|
31936
|
+
vertexData.push(vec3.x, vec3.y, u1, v1, tint, textureId);
|
|
31937
|
+
}
|
|
31938
|
+
};
|
|
31939
|
+
|
|
31940
|
+
// src/video/webgl/batchers/lit_quad_batcher.js
|
|
31941
|
+
var LitQuadBatcher = class extends QuadBatcher {
|
|
31942
|
+
/**
|
|
31943
|
+
* @ignore
|
|
31944
|
+
*/
|
|
31945
|
+
init(renderer2) {
|
|
31946
|
+
const halved = Math.min(
|
|
31947
|
+
Math.max(1, Math.floor(renderer2.maxTextures / 2)),
|
|
31948
|
+
16
|
|
31949
|
+
);
|
|
31950
|
+
this.maxBatchTextures = halved;
|
|
31951
|
+
Object.getPrototypeOf(QuadBatcher.prototype).init.call(this, renderer2, {
|
|
31952
|
+
attributes: [
|
|
31953
|
+
{
|
|
31954
|
+
name: "aVertex",
|
|
31955
|
+
size: 2,
|
|
31956
|
+
type: renderer2.gl.FLOAT,
|
|
31957
|
+
normalized: false,
|
|
31958
|
+
offset: 0 * Float32Array.BYTES_PER_ELEMENT
|
|
31959
|
+
},
|
|
31960
|
+
{
|
|
31961
|
+
name: "aRegion",
|
|
31962
|
+
size: 2,
|
|
31963
|
+
type: renderer2.gl.FLOAT,
|
|
31964
|
+
normalized: false,
|
|
31965
|
+
offset: 2 * Float32Array.BYTES_PER_ELEMENT
|
|
31966
|
+
},
|
|
31967
|
+
{
|
|
31968
|
+
name: "aColor",
|
|
31969
|
+
size: 4,
|
|
31970
|
+
type: renderer2.gl.UNSIGNED_BYTE,
|
|
31971
|
+
normalized: true,
|
|
31972
|
+
offset: 4 * Float32Array.BYTES_PER_ELEMENT
|
|
31973
|
+
},
|
|
31974
|
+
{
|
|
31975
|
+
name: "aTextureId",
|
|
31976
|
+
size: 1,
|
|
31977
|
+
type: renderer2.gl.FLOAT,
|
|
31978
|
+
normalized: false,
|
|
31979
|
+
offset: 5 * Float32Array.BYTES_PER_ELEMENT
|
|
31980
|
+
},
|
|
31981
|
+
{
|
|
31982
|
+
name: "aNormalTextureId",
|
|
31983
|
+
size: 1,
|
|
31984
|
+
type: renderer2.gl.FLOAT,
|
|
31985
|
+
normalized: false,
|
|
31986
|
+
offset: 6 * Float32Array.BYTES_PER_ELEMENT
|
|
31987
|
+
}
|
|
31988
|
+
],
|
|
31989
|
+
shader: {
|
|
31990
|
+
vertex: quad_multi_lit_default,
|
|
31991
|
+
fragment: buildLitMultiTextureFragment(halved)
|
|
31992
|
+
}
|
|
31993
|
+
});
|
|
31994
|
+
this.bindColorSamplers();
|
|
31995
|
+
this.bindNormalSamplers();
|
|
31996
|
+
this.createIndexBuffer();
|
|
31997
|
+
this.useMultiTexture = true;
|
|
31998
|
+
this.boundNormalMaps = new Array(halved).fill(null);
|
|
31999
|
+
this.normalMapTextures = /* @__PURE__ */ new Map();
|
|
32000
|
+
this._lightCount = 0;
|
|
32001
|
+
this._maxLights = MAX_LIGHTS;
|
|
32002
|
+
this.defaultShader.setUniform("uLightCount", 0);
|
|
32003
|
+
this.defaultShader.setUniform("uAmbient", [0, 0, 0]);
|
|
32004
|
+
}
|
|
32005
|
+
/**
|
|
32006
|
+
* Bind the paired normal sampler uniforms (`uNormalSampler0..N-1`)
|
|
32007
|
+
* to texture units `maxBatchTextures..2*maxBatchTextures-1`. Called
|
|
32008
|
+
* from `init` and `reset`.
|
|
32009
|
+
* @ignore
|
|
32010
|
+
*/
|
|
32011
|
+
bindNormalSamplers() {
|
|
32012
|
+
for (let i = 0; i < this.maxBatchTextures; i++) {
|
|
32013
|
+
this.defaultShader.setUniform(
|
|
32014
|
+
"uNormalSampler" + i,
|
|
32015
|
+
this.maxBatchTextures + i
|
|
32016
|
+
);
|
|
32017
|
+
}
|
|
32018
|
+
}
|
|
32019
|
+
/**
|
|
32020
|
+
* @ignore
|
|
32021
|
+
*/
|
|
32022
|
+
reset() {
|
|
32023
|
+
super.reset();
|
|
32024
|
+
this.bindNormalSamplers();
|
|
32025
|
+
this.boundNormalMaps.fill(null);
|
|
32026
|
+
this.normalMapTextures.clear();
|
|
32027
|
+
this._lightCount = 0;
|
|
32028
|
+
this.defaultShader.setUniform("uLightCount", 0);
|
|
32029
|
+
this.defaultShader.setUniform("uAmbient", [0, 0, 0]);
|
|
32030
|
+
}
|
|
32031
|
+
/**
|
|
32032
|
+
* Upload per-frame Light2d uniforms used by the lit fragment path.
|
|
32033
|
+
* Called once per camera per frame (before the world tree walk).
|
|
32034
|
+
* Lights past `MAX_LIGHTS` are silently ignored.
|
|
32035
|
+
*
|
|
32036
|
+
* Coordinates must be supplied in the same space as the renderer's
|
|
32037
|
+
* pre-projection vertex coords (i.e. camera-local / FBO-local),
|
|
32038
|
+
* matching `Stage.drawLighting`'s convention.
|
|
32039
|
+
* @param {object} uniforms
|
|
32040
|
+
* @param {Float32Array} uniforms.positions - flat array of `[x, y, radius, intensity]` per light, length = 4 * count
|
|
32041
|
+
* @param {Float32Array} uniforms.colors - flat array of `[r, g, b]` per light, length = 3 * count
|
|
32042
|
+
* @param {Float32Array} [uniforms.heights] - flat array of per-light height, length = MAX_LIGHTS
|
|
32043
|
+
* @param {number} uniforms.count - number of lights to render (clamped to MAX_LIGHTS)
|
|
32044
|
+
* @param {number[]} [uniforms.ambient] - `[r, g, b]` ambient floor (0..1 each)
|
|
32045
|
+
*/
|
|
32046
|
+
setLightUniforms(uniforms) {
|
|
32047
|
+
const shader = this.defaultShader;
|
|
32048
|
+
const count = Math.min(uniforms.count | 0, this._maxLights);
|
|
32049
|
+
this._lightCount = count;
|
|
32050
|
+
shader.setUniform("uLightCount", count);
|
|
32051
|
+
if (count > 0) {
|
|
32052
|
+
shader.setUniform("uLightPos", uniforms.positions);
|
|
32053
|
+
shader.setUniform("uLightColor", uniforms.colors);
|
|
32054
|
+
if (uniforms.heights) {
|
|
32055
|
+
shader.setUniform("uLightHeight", uniforms.heights);
|
|
32056
|
+
}
|
|
32057
|
+
}
|
|
32058
|
+
if (uniforms.ambient) {
|
|
32059
|
+
shader.setUniform("uAmbient", uniforms.ambient);
|
|
32060
|
+
}
|
|
32061
|
+
}
|
|
32062
|
+
/**
|
|
32063
|
+
* Bind a normal-map image to the given GL texture unit. Uploads on
|
|
32064
|
+
* first use (via `uploadNormalMap`) and rebinds the cached
|
|
32065
|
+
* `WebGLTexture` on subsequent calls. Mirrors the
|
|
32066
|
+
* `bindTexture2D` / `createTexture2D` split used by `MaterialBatcher`,
|
|
32067
|
+
* but for normal-map textures which live outside the color
|
|
32068
|
+
* `TextureCache` (cached per-image in `normalMapTextures`).
|
|
32069
|
+
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} image - normal-map source
|
|
32070
|
+
* @param {number} unit - GL texture unit (already offset by `maxBatchTextures`)
|
|
32071
|
+
*/
|
|
32072
|
+
bindNormalMap(image, unit) {
|
|
32073
|
+
const cached = this.normalMapTextures.get(image);
|
|
32074
|
+
if (typeof cached !== "undefined") {
|
|
32075
|
+
this.bindTexture2D(cached, unit, false);
|
|
32076
|
+
return;
|
|
32077
|
+
}
|
|
32078
|
+
this.uploadNormalMap(image, unit);
|
|
32079
|
+
}
|
|
32080
|
+
/**
|
|
32081
|
+
* Upload a normal-map image to GL and cache the resulting `WebGLTexture`
|
|
32082
|
+
* for future `bindNormalMap` calls. Not meant to be called directly —
|
|
32083
|
+
* `bindNormalMap` invokes this on the first use of a given image.
|
|
32084
|
+
*
|
|
32085
|
+
* `premultipliedAlpha = false` — normal maps store linear-encoded
|
|
32086
|
+
* surface normals; multiplying through alpha would corrupt the
|
|
32087
|
+
* encoding for any non-opaque texel.
|
|
32088
|
+
* @param {HTMLImageElement|HTMLCanvasElement|OffscreenCanvas|ImageBitmap} image - normal-map source
|
|
32089
|
+
* @param {number} unit - GL texture unit (already offset by `maxBatchTextures`)
|
|
32090
|
+
*/
|
|
32091
|
+
uploadNormalMap(image, unit) {
|
|
32092
|
+
const gl = this.gl;
|
|
32093
|
+
this.createTexture2D(
|
|
32094
|
+
unit,
|
|
32095
|
+
image,
|
|
32096
|
+
this.renderer.settings.antiAlias ? gl.LINEAR : gl.NEAREST,
|
|
32097
|
+
"no-repeat",
|
|
32098
|
+
image.width,
|
|
32099
|
+
image.height,
|
|
32100
|
+
false,
|
|
32101
|
+
void 0,
|
|
32102
|
+
void 0,
|
|
32103
|
+
false
|
|
32104
|
+
);
|
|
32105
|
+
this.normalMapTextures.set(image, this.boundTextures[unit]);
|
|
32106
|
+
}
|
|
32107
|
+
/**
|
|
32108
|
+
* Add a textured quad with optional paired normal map.
|
|
32109
|
+
* @param {TextureAtlas} texture - Source texture atlas
|
|
32110
|
+
* @param {number} x - Destination x-coordinate
|
|
32111
|
+
* @param {number} y - Destination y-coordinate
|
|
32112
|
+
* @param {number} w - Destination width
|
|
32113
|
+
* @param {number} h - Destination height
|
|
32114
|
+
* @param {number} u0 - Texture UV (u0) value
|
|
32115
|
+
* @param {number} v0 - Texture UV (v0) value
|
|
32116
|
+
* @param {number} u1 - Texture UV (u1) value
|
|
32117
|
+
* @param {number} v1 - Texture UV (v1) value
|
|
32118
|
+
* @param {number} tint - tint color (UINT32 argb)
|
|
32119
|
+
* @param {boolean} [reupload=false] - Force the texture to be reuploaded
|
|
32120
|
+
* @param {HTMLImageElement|HTMLCanvasElement|null} [normalMap=null] - paired normal-map (SpriteIlluminator workflow)
|
|
32121
|
+
*/
|
|
32122
|
+
addQuad(texture, x, y, w, h, u0, v0, u1, v1, tint, reupload = false, normalMap = null) {
|
|
32123
|
+
const vertexData = this.vertexData;
|
|
32124
|
+
if (vertexData.isFull(4)) {
|
|
32125
|
+
this.flush();
|
|
32126
|
+
}
|
|
32127
|
+
let unit;
|
|
32128
|
+
if (this.useMultiTexture) {
|
|
32129
|
+
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
32130
|
+
if (unit >= this.maxBatchTextures) {
|
|
32131
|
+
this.flush();
|
|
32132
|
+
this.renderer.cache.resetUnitAssignments();
|
|
32133
|
+
this.boundNormalMaps.fill(null);
|
|
32134
|
+
unit = this.uploadTexture(texture, w, h, reupload, false);
|
|
32135
|
+
}
|
|
32136
|
+
} else {
|
|
32137
|
+
unit = this.uploadTexture(texture, w, h, reupload);
|
|
32138
|
+
if (unit !== this.currentSamplerUnit) {
|
|
32139
|
+
this.currentShader.setUniform("uSampler", unit);
|
|
32140
|
+
this.currentSamplerUnit = unit;
|
|
32141
|
+
}
|
|
32142
|
+
}
|
|
32143
|
+
let normalTextureId = -1;
|
|
32144
|
+
if (normalMap !== null && this.useMultiTexture) {
|
|
32145
|
+
const normalUnit = this.maxBatchTextures + unit;
|
|
32146
|
+
const prev = this.boundNormalMaps[unit];
|
|
32147
|
+
if (prev !== normalMap) {
|
|
32148
|
+
if (prev !== null) {
|
|
32149
|
+
this.flush();
|
|
32150
|
+
}
|
|
32151
|
+
this.bindNormalMap(normalMap, normalUnit);
|
|
32152
|
+
this.boundNormalMaps[unit] = normalMap;
|
|
32153
|
+
}
|
|
32154
|
+
normalTextureId = unit;
|
|
32155
|
+
}
|
|
32156
|
+
const m = this.viewMatrix;
|
|
32157
|
+
const vec0 = V_ARRAY[0].set(x, y);
|
|
32158
|
+
const vec1 = V_ARRAY[1].set(x + w, y);
|
|
32159
|
+
const vec2 = V_ARRAY[2].set(x, y + h);
|
|
32160
|
+
const vec3 = V_ARRAY[3].set(x + w, y + h);
|
|
32161
|
+
if (!m.isIdentity()) {
|
|
32162
|
+
m.apply(vec0);
|
|
32163
|
+
m.apply(vec1);
|
|
32164
|
+
m.apply(vec2);
|
|
32165
|
+
m.apply(vec3);
|
|
32166
|
+
}
|
|
32167
|
+
const textureId = this.useMultiTexture ? unit : 0;
|
|
32168
|
+
vertexData.push(vec0.x, vec0.y, u0, v0, tint, textureId, normalTextureId);
|
|
32169
|
+
vertexData.push(vec1.x, vec1.y, u1, v0, tint, textureId, normalTextureId);
|
|
32170
|
+
vertexData.push(vec2.x, vec2.y, u0, v1, tint, textureId, normalTextureId);
|
|
32171
|
+
vertexData.push(vec3.x, vec3.y, u1, v1, tint, textureId, normalTextureId);
|
|
32172
|
+
}
|
|
32173
|
+
/**
|
|
32174
|
+
* Override `blitTexture` so the FBO blit pushes `-1` as the unlit
|
|
32175
|
+
* sentinel (this batcher's vertex layout includes `aNormalTextureId`).
|
|
32176
|
+
* @param {WebGLTexture} source - the raw GL texture to blit
|
|
32177
|
+
* @param {number} x - destination x
|
|
32178
|
+
* @param {number} y - destination y
|
|
32179
|
+
* @param {number} width - destination width
|
|
32180
|
+
* @param {number} height - destination height
|
|
32181
|
+
* @param {GLShader|ShaderEffect} shader - the shader effect to apply
|
|
32182
|
+
*/
|
|
32183
|
+
blitTexture(source, x, y, width, height, shader) {
|
|
32184
|
+
const gl = this.gl;
|
|
32185
|
+
this.useShader(shader);
|
|
32186
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
32187
|
+
gl.bindTexture(gl.TEXTURE_2D, source);
|
|
32188
|
+
this.currentTextureUnit = 0;
|
|
32189
|
+
this.boundTextures[0] = source;
|
|
32190
|
+
shader.setUniform("uSampler", 0);
|
|
32191
|
+
const m = this.viewMatrix;
|
|
32192
|
+
const vec0 = V_ARRAY[0].set(x, y);
|
|
32193
|
+
const vec1 = V_ARRAY[1].set(x + width, y);
|
|
32194
|
+
const vec2 = V_ARRAY[2].set(x, y + height);
|
|
32195
|
+
const vec3 = V_ARRAY[3].set(x + width, y + height);
|
|
32196
|
+
if (m && !m.isIdentity()) {
|
|
32197
|
+
m.apply(vec0);
|
|
32198
|
+
m.apply(vec1);
|
|
32199
|
+
m.apply(vec2);
|
|
32200
|
+
m.apply(vec3);
|
|
32201
|
+
}
|
|
32202
|
+
const tint = 4294967295;
|
|
32203
|
+
this.vertexData.push(vec0.x, vec0.y, 0, 1, tint, 0, -1);
|
|
32204
|
+
this.vertexData.push(vec1.x, vec1.y, 1, 1, tint, 0, -1);
|
|
32205
|
+
this.vertexData.push(vec2.x, vec2.y, 0, 0, tint, 0, -1);
|
|
32206
|
+
this.vertexData.push(vec3.x, vec3.y, 1, 0, tint, 0, -1);
|
|
32207
|
+
this.flush();
|
|
32208
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
32209
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
32210
|
+
this.currentTextureUnit = -1;
|
|
32211
|
+
delete this.boundTextures[0];
|
|
32212
|
+
this.useShader(this.defaultShader);
|
|
32213
|
+
}
|
|
32214
|
+
};
|
|
32215
|
+
|
|
32216
|
+
// src/video/webgl/shaders/mesh.frag
|
|
32217
|
+
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";
|
|
32218
|
+
|
|
32219
|
+
// src/video/webgl/shaders/mesh.vert
|
|
32220
|
+
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";
|
|
32221
|
+
|
|
30670
32222
|
// src/video/webgl/batchers/mesh_batcher.js
|
|
30671
32223
|
var _v = new Vector2d();
|
|
30672
32224
|
var MeshBatcher = class extends MaterialBatcher {
|
|
@@ -30940,239 +32492,509 @@ var PrimitiveBatcher = class extends Batcher {
|
|
|
30940
32492
|
}
|
|
30941
32493
|
};
|
|
30942
32494
|
|
|
30943
|
-
// src/video/webgl/
|
|
30944
|
-
|
|
30945
|
-
|
|
30946
|
-
|
|
30947
|
-
|
|
30948
|
-
|
|
30949
|
-
|
|
30950
|
-
|
|
30951
|
-
|
|
30952
|
-
|
|
30953
|
-
|
|
30954
|
-
|
|
30955
|
-
|
|
30956
|
-
|
|
30957
|
-
|
|
30958
|
-
|
|
32495
|
+
// src/video/webgl/effects/radialGradient.js
|
|
32496
|
+
var RadialGradientEffect = class extends ShaderEffect {
|
|
32497
|
+
/**
|
|
32498
|
+
* @param {WebGLRenderer} renderer - the current renderer instance
|
|
32499
|
+
* @param {object} [options] - initial uniform values
|
|
32500
|
+
* @param {Color} [options.color] - center color (0..255 RGB); defaults to white
|
|
32501
|
+
* @param {number} [options.intensity=1] - peak alpha at the center (0..1+)
|
|
32502
|
+
*/
|
|
32503
|
+
constructor(renderer2, options = {}) {
|
|
32504
|
+
super(
|
|
32505
|
+
renderer2,
|
|
32506
|
+
`
|
|
32507
|
+
uniform vec3 uColor;
|
|
32508
|
+
uniform float uIntensity;
|
|
32509
|
+
vec4 apply(vec4 color, vec2 uv) {
|
|
32510
|
+
// recenter to [-1, 1] across the quad. The quad's own aspect
|
|
32511
|
+
// ratio handles elliptical falloffs naturally \u2014 length(c) == 1
|
|
32512
|
+
// lies on the inscribed ellipse in world space.
|
|
32513
|
+
vec2 c = uv * 2.0 - 1.0;
|
|
32514
|
+
float d = length(c);
|
|
32515
|
+
// linear ramp matches Canvas createRadialGradient's two-stop output
|
|
32516
|
+
float f = clamp(1.0 - d, 0.0, 1.0);
|
|
32517
|
+
// 'color' is the per-vertex tint, already premultiplied by
|
|
32518
|
+
// alpha in the vertex shader (vColor = vec4(aColor.bgr *
|
|
32519
|
+
// aColor.a, aColor.a)). For standalone use the tint is
|
|
32520
|
+
// (1,1,1,1) and the uniforms drive the look; for the Light2d
|
|
32521
|
+
// batching path the uniforms stay at default and the tint
|
|
32522
|
+
// carries the per-light color + intensity.
|
|
32523
|
+
vec3 rgb = color.rgb * uColor * uIntensity * f;
|
|
32524
|
+
float a = color.a * uIntensity * f;
|
|
32525
|
+
return vec4(rgb, a);
|
|
32526
|
+
}
|
|
32527
|
+
`
|
|
32528
|
+
);
|
|
32529
|
+
this._colorBuf = new Float32Array(3);
|
|
32530
|
+
const color = options.color;
|
|
32531
|
+
if (color) {
|
|
32532
|
+
this.setColor(color);
|
|
30959
32533
|
} else {
|
|
30960
|
-
|
|
32534
|
+
this._colorBuf[0] = 1;
|
|
32535
|
+
this._colorBuf[1] = 1;
|
|
32536
|
+
this._colorBuf[2] = 1;
|
|
32537
|
+
this.setUniform("uColor", this._colorBuf);
|
|
30961
32538
|
}
|
|
30962
|
-
|
|
32539
|
+
this.setIntensity(options.intensity ?? 1);
|
|
30963
32540
|
}
|
|
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 {
|
|
30983
32541
|
/**
|
|
30984
|
-
*
|
|
30985
|
-
*
|
|
32542
|
+
* Set the center color. RGB only — alpha is ignored (the radial
|
|
32543
|
+
* falloff supplies the per-pixel alpha).
|
|
32544
|
+
* @param {Color} color - 0..255 RGB color
|
|
30986
32545
|
*/
|
|
30987
|
-
|
|
30988
|
-
this.
|
|
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
|
-
|
|
31014
|
-
|
|
31015
|
-
|
|
31016
|
-
|
|
31017
|
-
|
|
31018
|
-
|
|
31019
|
-
|
|
31020
|
-
|
|
31021
|
-
vertex: quad_multi_default,
|
|
31022
|
-
fragment: buildMultiTextureFragment(this.maxBatchTextures)
|
|
32546
|
+
setColor(color) {
|
|
32547
|
+
this._colorBuf[0] = color.r / 255;
|
|
32548
|
+
this._colorBuf[1] = color.g / 255;
|
|
32549
|
+
this._colorBuf[2] = color.b / 255;
|
|
32550
|
+
this.setUniform("uColor", this._colorBuf);
|
|
32551
|
+
}
|
|
32552
|
+
/**
|
|
32553
|
+
* Set the peak intensity. Acts as a brightness multiplier on the
|
|
32554
|
+
* falloff curve; values above 1 over-saturate the center of the gradient.
|
|
32555
|
+
* @param {number} intensity - 0..1+ multiplier
|
|
32556
|
+
*/
|
|
32557
|
+
setIntensity(intensity) {
|
|
32558
|
+
this.setUniform("uIntensity", intensity);
|
|
32559
|
+
}
|
|
32560
|
+
};
|
|
32561
|
+
|
|
32562
|
+
// src/video/webgl/lighting/pack.ts
|
|
32563
|
+
function createLightUniformScratch() {
|
|
32564
|
+
return {
|
|
32565
|
+
positions: new Float32Array(MAX_LIGHTS * 4),
|
|
32566
|
+
colors: new Float32Array(MAX_LIGHTS * 3),
|
|
32567
|
+
heights: new Float32Array(MAX_LIGHTS),
|
|
32568
|
+
ambient: [0, 0, 0]
|
|
32569
|
+
};
|
|
32570
|
+
}
|
|
32571
|
+
function packLights(lights, ambient, translateX, translateY, scratch) {
|
|
32572
|
+
scratch.positions.fill(0);
|
|
32573
|
+
scratch.colors.fill(0);
|
|
32574
|
+
scratch.heights.fill(0);
|
|
32575
|
+
let i = 0;
|
|
32576
|
+
if (lights) {
|
|
32577
|
+
for (const light of lights) {
|
|
32578
|
+
if (i >= MAX_LIGHTS) {
|
|
32579
|
+
break;
|
|
31023
32580
|
}
|
|
31024
|
-
|
|
31025
|
-
|
|
31026
|
-
|
|
32581
|
+
const b = light.getBounds();
|
|
32582
|
+
const radius = Math.max(b.width, b.height) / 2;
|
|
32583
|
+
scratch.positions[i * 4 + 0] = b.centerX - translateX;
|
|
32584
|
+
scratch.positions[i * 4 + 1] = b.centerY - translateY;
|
|
32585
|
+
scratch.positions[i * 4 + 2] = radius;
|
|
32586
|
+
scratch.positions[i * 4 + 3] = light.intensity;
|
|
32587
|
+
scratch.colors[i * 3 + 0] = light.color.r / 255;
|
|
32588
|
+
scratch.colors[i * 3 + 1] = light.color.g / 255;
|
|
32589
|
+
scratch.colors[i * 3 + 2] = light.color.b / 255;
|
|
32590
|
+
scratch.heights[i] = light.lightHeight;
|
|
32591
|
+
i++;
|
|
31027
32592
|
}
|
|
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
32593
|
}
|
|
32594
|
+
if (ambient) {
|
|
32595
|
+
scratch.ambient[0] = ambient.r / 255;
|
|
32596
|
+
scratch.ambient[1] = ambient.g / 255;
|
|
32597
|
+
scratch.ambient[2] = ambient.b / 255;
|
|
32598
|
+
} else {
|
|
32599
|
+
scratch.ambient[0] = 0;
|
|
32600
|
+
scratch.ambient[1] = 0;
|
|
32601
|
+
scratch.ambient[2] = 0;
|
|
32602
|
+
}
|
|
32603
|
+
return {
|
|
32604
|
+
positions: scratch.positions,
|
|
32605
|
+
colors: scratch.colors,
|
|
32606
|
+
heights: scratch.heights,
|
|
32607
|
+
ambient: scratch.ambient,
|
|
32608
|
+
count: i
|
|
32609
|
+
};
|
|
32610
|
+
}
|
|
32611
|
+
|
|
32612
|
+
// src/video/texture/resource.js
|
|
32613
|
+
var TextureResource = class {
|
|
31037
32614
|
/**
|
|
31038
|
-
*
|
|
31039
|
-
*
|
|
31040
|
-
*
|
|
31041
|
-
* @
|
|
31042
|
-
* @
|
|
31043
|
-
* @param {
|
|
32615
|
+
* @param {object} options
|
|
32616
|
+
* @param {number} options.width - pixel width of the texture
|
|
32617
|
+
* @param {number} options.height - pixel height of the texture
|
|
32618
|
+
* @param {boolean} [options.premultipliedAlpha=false]
|
|
32619
|
+
* @param {string} [options.repeat="no-repeat"] - "no-repeat" | "repeat" | "repeat-x" | "repeat-y"
|
|
32620
|
+
* @param {number} [options.filter] - `gl.NEAREST` or `gl.LINEAR`; when
|
|
32621
|
+
* omitted the batcher falls back to the renderer's `antiAlias` setting
|
|
31044
32622
|
*/
|
|
31045
|
-
|
|
31046
|
-
|
|
31047
|
-
|
|
32623
|
+
constructor({
|
|
32624
|
+
width,
|
|
32625
|
+
height,
|
|
32626
|
+
premultipliedAlpha = false,
|
|
32627
|
+
repeat = "no-repeat",
|
|
32628
|
+
filter
|
|
32629
|
+
} = {}) {
|
|
32630
|
+
this.width = width;
|
|
32631
|
+
this.height = height;
|
|
32632
|
+
this.premultipliedAlpha = premultipliedAlpha;
|
|
32633
|
+
this.repeat = repeat;
|
|
32634
|
+
this.filter = filter;
|
|
32635
|
+
this.sources = /* @__PURE__ */ new Map([["default", this]]);
|
|
32636
|
+
this.activeAtlas = "default";
|
|
31048
32637
|
}
|
|
31049
32638
|
/**
|
|
31050
|
-
*
|
|
32639
|
+
* Returns the upload "source" the batcher hands to `createTexture2D`.
|
|
32640
|
+
* For a resource this is the resource itself — `createTexture2D`
|
|
32641
|
+
* dispatches to `resource.upload(gl, target)`.
|
|
31051
32642
|
* @ignore
|
|
31052
32643
|
*/
|
|
31053
|
-
|
|
31054
|
-
|
|
31055
|
-
const maxQuads = this.vertexData.maxVertex / 4;
|
|
31056
|
-
this.indexBuffer = new WebGLIndexBuffer(
|
|
31057
|
-
this.gl,
|
|
31058
|
-
maxQuads * 6,
|
|
31059
|
-
this.renderer.WebGLVersion > 1
|
|
31060
|
-
);
|
|
31061
|
-
this.indexBuffer.fillQuadPattern(maxQuads);
|
|
31062
|
-
for (let i = 0; i < this.maxBatchTextures; i++) {
|
|
31063
|
-
this.defaultShader.setUniform("uSampler" + i, i);
|
|
31064
|
-
}
|
|
31065
|
-
this.useMultiTexture = true;
|
|
32644
|
+
getTexture() {
|
|
32645
|
+
return this;
|
|
31066
32646
|
}
|
|
31067
32647
|
/**
|
|
31068
|
-
*
|
|
31069
|
-
*
|
|
32648
|
+
* Issue the `gl.texImage2D` (or equivalent) call that uploads this
|
|
32649
|
+
* resource's data into the currently-bound `TEXTURE_2D` slot.
|
|
32650
|
+
* Subclasses MUST override.
|
|
32651
|
+
* @abstract
|
|
32652
|
+
* @param {WebGLRenderingContext|WebGL2RenderingContext} gl
|
|
32653
|
+
* @param {number} target - `gl.TEXTURE_2D` (or future cube-map targets)
|
|
31070
32654
|
*/
|
|
31071
|
-
|
|
31072
|
-
|
|
31073
|
-
|
|
31074
|
-
|
|
31075
|
-
|
|
31076
|
-
|
|
31077
|
-
|
|
31078
|
-
|
|
31079
|
-
|
|
31080
|
-
|
|
31081
|
-
|
|
31082
|
-
|
|
31083
|
-
|
|
31084
|
-
|
|
32655
|
+
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
|
32656
|
+
upload(gl, target) {
|
|
32657
|
+
throw new Error("TextureResource subclasses must implement upload()");
|
|
32658
|
+
}
|
|
32659
|
+
};
|
|
32660
|
+
var BufferTextureResource = class extends TextureResource {
|
|
32661
|
+
/**
|
|
32662
|
+
* @param {ArrayBufferView} data - the pixel data; size must be
|
|
32663
|
+
* `width * height * 4` bytes for the default RGBA / UNSIGNED_BYTE
|
|
32664
|
+
* format
|
|
32665
|
+
* @param {object} options
|
|
32666
|
+
* @param {number} options.width
|
|
32667
|
+
* @param {number} options.height
|
|
32668
|
+
* @param {boolean} [options.premultipliedAlpha=false]
|
|
32669
|
+
* @param {string} [options.repeat="no-repeat"]
|
|
32670
|
+
* @param {number} [options.filter]
|
|
32671
|
+
* @param {"rgba8"|"rgba8ui"} [options.format="rgba8"] - storage format.
|
|
32672
|
+
* `"rgba8"` (default): normalized RGBA, sampled via `sampler2D` /
|
|
32673
|
+
* `texture()`. `"rgba8ui"`: unsigned-integer RGBA, sampled via
|
|
32674
|
+
* `usampler2D` / `texelFetch()` — requires WebGL2. Use the integer
|
|
32675
|
+
* form for raw-data lookups (GID tables, palette indices, etc.) to
|
|
32676
|
+
* skip the float-decode round trip and gain exact integer reads.
|
|
32677
|
+
*/
|
|
32678
|
+
constructor(data2, options) {
|
|
32679
|
+
super(options);
|
|
32680
|
+
this.data = data2;
|
|
32681
|
+
this.format = options.format || "rgba8";
|
|
32682
|
+
}
|
|
32683
|
+
/** @ignore */
|
|
32684
|
+
upload(gl, target) {
|
|
32685
|
+
if (this.format === "rgba8ui") {
|
|
32686
|
+
if (typeof gl.RGBA8UI === "undefined") {
|
|
32687
|
+
throw new Error(
|
|
32688
|
+
'BufferTextureResource: format "rgba8ui" requires a WebGL 2 context'
|
|
31085
32689
|
);
|
|
32690
|
+
}
|
|
32691
|
+
gl.texImage2D(
|
|
32692
|
+
target,
|
|
32693
|
+
0,
|
|
32694
|
+
gl.RGBA8UI,
|
|
32695
|
+
this.width,
|
|
32696
|
+
this.height,
|
|
32697
|
+
0,
|
|
32698
|
+
gl.RGBA_INTEGER,
|
|
32699
|
+
gl.UNSIGNED_BYTE,
|
|
32700
|
+
this.data
|
|
32701
|
+
);
|
|
32702
|
+
} else {
|
|
32703
|
+
gl.texImage2D(
|
|
32704
|
+
target,
|
|
32705
|
+
0,
|
|
32706
|
+
gl.RGBA,
|
|
32707
|
+
this.width,
|
|
32708
|
+
this.height,
|
|
32709
|
+
0,
|
|
32710
|
+
gl.RGBA,
|
|
32711
|
+
gl.UNSIGNED_BYTE,
|
|
32712
|
+
this.data
|
|
32713
|
+
);
|
|
32714
|
+
}
|
|
32715
|
+
}
|
|
32716
|
+
};
|
|
32717
|
+
|
|
32718
|
+
// src/video/webgl/shaders/orthogonal-tmxlayer.frag
|
|
32719
|
+
var orthogonal_tmxlayer_default = "#version 300 es\n\n// Fragment shader for the orthogonal TMX layer GPU renderer (WebGL2 /\n// GLSL ES 3.00).\n//\n// Per fragment the shader:\n// 1. recovers the world-pixel position from the host UV,\n// 2. walks candidate cells (geometric cell + cells whose oversized,\n// bottom-aligned tiles could reach this fragment),\n// 3. fetches GIDs from the per-layer index texture, and\n// 4. samples the tileset atlas at the correct sub-region.\n//\n// Fast path: when the tileset has no oversized tiles (`uOverflow == (0, 0)`)\n// \u2014 the common case for grid-aligned maps \u2014 only the geometric cell can\n// hold this fragment's tile, so we skip the candidate loop entirely and\n// run a single `tryRenderCell` call. The loop branch is on a uniform\n// value, coherent across the wave, and trims 25 worst-case loop\n// iterations + their guard checks to a single inlined function call.\n//\n// Slow path: tiles drawing larger than the cell are bottom-aligned\n// vertically and left-aligned horizontally. Render order is \"right-down\":\n// later cells end up on top, so the candidate loop scans dy high\u2192low,\n// dx low\u2192high and picks the FIRST match.\n//\n// Index texture encoding (`RGBA8`, one cell per texel):\n// R = GID low byte\n// G = GID high byte (combined: R | (G << 8) = 16-bit GID)\n// B = flip mask (bit 0 = H, bit 1 = V, bit 2 = AD)\n// A = unused\n//\n// Animation lookup (`RGBA8`, 1 row, `tileCount` texels wide): per local\n// tile id, the CURRENT frame's local id, same R/G byte-pair encoding.\n//\n// Why `sampler2D` + float decode rather than `usampler2D`: the engine's\n// multi-texture default shader declares `uSampler0..uSamplerN-1` as\n// `sampler2D` \u2014 all of them are active for WebGL's draw-time validation.\n// A `usampler2D`-backed `RGBA8UI` texture cached at any of those units\n// (units 0..15 on a typical 16-unit fragment stage) would mismatch when\n// the default shader next draws sprites, killing every quad with\n// `GL_INVALID_OPERATION`. Staying on regular RGBA8 keeps the cache\n// path coherent \u2014 the cost is one `floor(c * 255 + 0.5)` per fetch.\n//\n// `texelFetch` is still used (vs `texture()`) for byte-exact reads \u2014\n// it bypasses interpolation, so the integer byte values come out\n// unmolested even on a normalized-float sampler.\n\n// The engine's `setPrecision` step injects precision declarations for\n// float and int after the `#version` line, using whatever precision the\n// renderer was configured with (`highPrecisionShader` setting on the\n// Application). Individual shader files don't hardcode precision so the\n// engine-wide preference applies.\n\nin vec2 vRegion;\nin vec4 vColor;\n\nuniform sampler2D uSampler; // tileset atlas (RGBA)\nuniform sampler2D uTileIndex; // per-layer GID index (RGBA8)\nuniform sampler2D uAnimLookup; // per-tileset animation table (RGBA8)\n\nuniform vec2 uMapSize;\nuniform vec2 uCellSize;\nuniform vec2 uTileSize;\nuniform vec2 uOverflow;\nuniform vec2 uTilesetCols;\nuniform vec2 uInvTilesetSize;\nuniform vec4 uTilesetMargin; // (marginX, marginY, spacingX, spacingY)\nuniform vec2 uGidRange; // (firstgid, lastgid)\nuniform vec2 uVisibleStart;\nuniform vec2 uVisibleSize;\nuniform int uAnimSize; // number of entries in uAnimLookup, 0 if disabled\nuniform float uOpacity;\nuniform vec4 uTint;\n\nout vec4 fragColor;\n\nconst int MAX_OVERFLOW = 4;\n\n// Try to render the tile at cell (cx, cy) for the current fragment.\n// Returns true and writes the sampled color to `outColor` when the cell\n// contains a visible, in-range tile whose pixel covers this fragment.\n// Identical logic for both fast and slow paths \u2014 GLSL inlines this\n// trivially so there's no function-call overhead at runtime.\nbool tryRenderCell(int cx, int cy, vec2 worldPx, out vec4 outColor) {\n int mapW = int(uMapSize.x);\n int mapH = int(uMapSize.y);\n if (cx < 0 || cx >= mapW || cy < 0 || cy >= mapH) {\n return false;\n }\n\n // `texelFetch` skips filtering \u2014 the 8-bit channel values come back\n // as normalized floats, decoded to byte ints below\n vec4 cellF = texelFetch(uTileIndex, ivec2(cx, cy), 0);\n uvec4 cell = uvec4(cellF * 255.0 + 0.5);\n int firstGid = int(uGidRange.x);\n int gid = int(cell.r) | (int(cell.g) << 8);\n if (gid < firstGid || gid > int(uGidRange.y)) {\n return false;\n }\n\n vec2 tileWorldOrigin = vec2(\n float(cx) * uCellSize.x,\n (float(cy) + 1.0) * uCellSize.y - uTileSize.y\n );\n vec2 inTile = (worldPx - tileWorldOrigin) / uTileSize;\n if (inTile.x < 0.0 || inTile.x >= 1.0 || inTile.y < 0.0 || inTile.y >= 1.0) {\n return false;\n }\n\n // flip mask + axis-swap trick: see TMX shader-path flip spec.\n // AD performs a transpose; with AD set, H and V swap their effective\n // axes (matches the legacy `buildFlipTransform`).\n int flipMask = int(cell.b);\n float flipH = float(flipMask & 1);\n float flipV = float((flipMask >> 1) & 1);\n float flipAD = float((flipMask >> 2) & 1);\n inTile = mix(inTile, inTile.yx, flipAD);\n float effH = mix(flipH, flipV, flipAD);\n float effV = mix(flipV, flipH, flipAD);\n inTile.x = mix(inTile.x, 1.0 - inTile.x, effH);\n inTile.y = mix(inTile.y, 1.0 - inTile.y, effV);\n\n int localId = gid - firstGid;\n\n // animation: if the tileset has animated tiles, swap the local id for\n // its current frame's id via the lookup texture (CPU updates the\n // lookup in lockstep with `tileset.update(dt)`).\n if (uAnimSize > 0) {\n vec4 animF = texelFetch(uAnimLookup, ivec2(localId, 0), 0);\n uvec4 animTexel = uvec4(animF * 255.0 + 0.5);\n localId = int(animTexel.r) | (int(animTexel.g) << 8);\n }\n\n float row = floor(float(localId) / uTilesetCols.x);\n float col = float(localId) - row * uTilesetCols.x;\n vec2 tileOriginPx = uTilesetMargin.xy\n + vec2(col, row) * (uTileSize + uTilesetMargin.zw);\n vec2 texelPx = tileOriginPx + inTile * uTileSize;\n vec2 texelUV = texelPx * uInvTilesetSize;\n\n vec4 sampled = texture(uSampler, texelUV);\n if (sampled.a <= 0.0) {\n return false;\n }\n outColor = sampled;\n return true;\n}\n\nvoid main(void) {\n vec2 tileCoord = uVisibleStart + vRegion * uVisibleSize;\n vec2 geomCell = floor(tileCoord);\n vec2 worldPx = tileCoord * uCellSize;\n\n int gx = int(geomCell.x);\n int gy = int(geomCell.y);\n int overflowX = int(uOverflow.x + 0.5);\n int overflowY = int(uOverflow.y + 0.5);\n\n vec4 result;\n\n // Fast path: tiles fit the cell exactly (the common case) \u2014 only the\n // geometric cell can contain this fragment's tile.\n if (overflowX == 0 && overflowY == 0) {\n if (!tryRenderCell(gx, gy, worldPx, result)) {\n discard;\n }\n result.a *= uOpacity;\n fragColor = result * uTint;\n return;\n }\n\n // Slow path: oversized tiles. Walk candidates dy high\u2192low, dx low\u2192high\n // and pick the FIRST match \u2014 render order is \"right-down\" so later\n // cells go on top.\n bool found = false;\n for (int idy = 0; idy <= MAX_OVERFLOW; idy++) {\n int dy = MAX_OVERFLOW - idy;\n if (dy > overflowY) continue;\n for (int dx = 0; dx <= MAX_OVERFLOW; dx++) {\n if (dx > overflowX) break;\n if (tryRenderCell(gx - dx, gy + dy, worldPx, result)) {\n found = true;\n break;\n }\n }\n if (found) break;\n }\n if (!found) discard;\n result.a *= uOpacity;\n fragColor = result * uTint;\n}\n";
|
|
32720
|
+
|
|
32721
|
+
// src/video/webgl/shaders/orthogonal-tmxlayer.vert
|
|
32722
|
+
var orthogonal_tmxlayer_default2 = '#version 300 es\n\n// Vertex shader for the orthogonal TMX layer GPU renderer.\n//\n// Matches the quad batcher\'s vertex layout (`aVertex`, `aRegion`, `aColor`,\n// `uProjectionMatrix`) so the standard `setBatcher("quad", this.shader)` +\n// `addQuad()` flow drives it like any other quad. Same attribute names,\n// same uniforms \u2014 just expressed in GLSL ES 3.00 (`in`/`out` in place of\n// `attribute`/`varying`) so the program can pair with the 3.00 fragment\n// shader that uses `usampler2D` / `texelFetch` for integer-typed lookups.\n\nin vec2 aVertex;\nin vec2 aRegion;\nin vec4 aColor;\n\nuniform mat4 uProjectionMatrix;\n\nout vec2 vRegion;\nout vec4 vColor;\n\nvoid main(void) {\n gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0);\n // premultiplied-alpha + bgra \u2192 rgba swap, same convention the batcher\n // uses for its default sprite shader\n vColor = vec4(aColor.bgr * aColor.a, aColor.a);\n vRegion = aRegion;\n}\n';
|
|
32723
|
+
|
|
32724
|
+
// src/video/webgl/renderers/tmxlayer/orthogonal.js
|
|
32725
|
+
var DEFAULT_TINT = new Float32Array([1, 1, 1, 1]);
|
|
32726
|
+
var OrthogonalTMXLayerGPURenderer = class {
|
|
32727
|
+
/**
|
|
32728
|
+
* @param {WebGLRenderer} renderer - the WebGL renderer instance
|
|
32729
|
+
*/
|
|
32730
|
+
constructor(renderer2) {
|
|
32731
|
+
this.renderer = renderer2;
|
|
32732
|
+
this.gl = renderer2.gl;
|
|
32733
|
+
this.shader = new GLShader(
|
|
32734
|
+
renderer2.gl,
|
|
32735
|
+
orthogonal_tmxlayer_default2,
|
|
32736
|
+
orthogonal_tmxlayer_default,
|
|
32737
|
+
renderer2.shaderPrecision
|
|
32738
|
+
);
|
|
32739
|
+
this.resources = /* @__PURE__ */ new Map();
|
|
32740
|
+
this.animLookups = /* @__PURE__ */ new Map();
|
|
32741
|
+
this._v2 = new Float32Array(2);
|
|
32742
|
+
this._v4 = new Float32Array(4);
|
|
32743
|
+
}
|
|
32744
|
+
/**
|
|
32745
|
+
* Free every cached per-layer index texture and empty the local
|
|
32746
|
+
* resource map. Called from `WebGLRenderer.reset()` (which
|
|
32747
|
+
* `GAME_RESET` triggers) so each level transition starts clean.
|
|
32748
|
+
* @ignore
|
|
32749
|
+
*/
|
|
32750
|
+
reset() {
|
|
32751
|
+
const batcher = this.renderer.currentBatcher;
|
|
32752
|
+
const cache2 = this.renderer.cache;
|
|
32753
|
+
const drop = (resource) => {
|
|
32754
|
+
if (batcher !== void 0) {
|
|
32755
|
+
batcher.deleteTexture2D(resource);
|
|
31086
32756
|
} else {
|
|
31087
|
-
|
|
31088
|
-
|
|
31089
|
-
vertex.toFloat32(0, vertexCount * vertexSize),
|
|
31090
|
-
gl.STREAM_DRAW
|
|
31091
|
-
);
|
|
32757
|
+
cache2.freeTextureUnit(resource);
|
|
32758
|
+
cache2.delete(resource);
|
|
31092
32759
|
}
|
|
31093
|
-
|
|
31094
|
-
|
|
31095
|
-
|
|
32760
|
+
};
|
|
32761
|
+
for (const resource of this.resources.values()) {
|
|
32762
|
+
drop(resource);
|
|
31096
32763
|
}
|
|
32764
|
+
for (const entry of this.animLookups.values()) {
|
|
32765
|
+
drop(entry.resource);
|
|
32766
|
+
}
|
|
32767
|
+
this.resources.clear();
|
|
32768
|
+
this.animLookups.clear();
|
|
31097
32769
|
}
|
|
31098
32770
|
/**
|
|
31099
|
-
*
|
|
31100
|
-
*
|
|
31101
|
-
*
|
|
31102
|
-
*
|
|
31103
|
-
* @param {
|
|
31104
|
-
* @param {number}
|
|
31105
|
-
* @param {number}
|
|
31106
|
-
* @
|
|
31107
|
-
* @param {GLShader|ShaderEffect} shader - the shader effect to apply
|
|
32771
|
+
* Write a `vec2` uniform without allocating a fresh Float32Array per
|
|
32772
|
+
* call. Both components flow into the shared `_v2` scratch buffer,
|
|
32773
|
+
* which `setUniform` reads synchronously and forwards to
|
|
32774
|
+
* `gl.uniform2fv` — so reusing the buffer across calls is safe.
|
|
32775
|
+
* @param {string} name
|
|
32776
|
+
* @param {number} x
|
|
32777
|
+
* @param {number} y
|
|
32778
|
+
* @private
|
|
31108
32779
|
*/
|
|
31109
|
-
|
|
31110
|
-
|
|
31111
|
-
this.
|
|
31112
|
-
|
|
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);
|
|
32780
|
+
_setV2(name, x, y) {
|
|
32781
|
+
this._v2[0] = x;
|
|
32782
|
+
this._v2[1] = y;
|
|
32783
|
+
this.shader.setUniform(name, this._v2);
|
|
31125
32784
|
}
|
|
31126
32785
|
/**
|
|
31127
|
-
*
|
|
31128
|
-
* @param {
|
|
31129
|
-
* @param {number} x
|
|
31130
|
-
* @param {number} y
|
|
31131
|
-
* @param {number}
|
|
31132
|
-
* @param {number}
|
|
31133
|
-
* @
|
|
31134
|
-
* @param {number} v0 - Texture UV (v0) value.
|
|
31135
|
-
* @param {number} u1 - Texture UV (u1) value.
|
|
31136
|
-
* @param {number} v1 - Texture UV (v1) value.
|
|
31137
|
-
* @param {number} tint - tint color to be applied to the texture in UINT32 (argb) format
|
|
31138
|
-
* @param {boolean} reupload - Force the texture to be reuploaded even if already bound
|
|
32786
|
+
* `vec4` counterpart to {@link _setV2}.
|
|
32787
|
+
* @param {string} name
|
|
32788
|
+
* @param {number} x
|
|
32789
|
+
* @param {number} y
|
|
32790
|
+
* @param {number} z
|
|
32791
|
+
* @param {number} w
|
|
32792
|
+
* @private
|
|
31139
32793
|
*/
|
|
31140
|
-
|
|
31141
|
-
|
|
31142
|
-
|
|
31143
|
-
|
|
32794
|
+
_setV4(name, x, y, z, w) {
|
|
32795
|
+
this._v4[0] = x;
|
|
32796
|
+
this._v4[1] = y;
|
|
32797
|
+
this._v4[2] = z;
|
|
32798
|
+
this._v4[3] = w;
|
|
32799
|
+
this.shader.setUniform(name, this._v4);
|
|
32800
|
+
}
|
|
32801
|
+
/**
|
|
32802
|
+
* Get-or-create the per-tileset animation-lookup entry. Returns
|
|
32803
|
+
* `undefined` for tilesets that have no animated tiles (the shader's
|
|
32804
|
+
* `uAnimEnabled` uniform is then set to 0 and the lookup texture is
|
|
32805
|
+
* not bound).
|
|
32806
|
+
*
|
|
32807
|
+
* The entry holds a `tileCount × 1` RGBA8 `BufferTextureResource`
|
|
32808
|
+
* where texel `localId` encodes the CURRENT frame's local id as
|
|
32809
|
+
* `R = lo byte, G = hi byte` (same encoding as the GID index
|
|
32810
|
+
* texture). Each call walks `tileset.animations` and rewrites
|
|
32811
|
+
* dirty texels — `tileset.update(dt)` (driven by the layer) advances
|
|
32812
|
+
* `anim.cur.tileid` independently of this renderer.
|
|
32813
|
+
*
|
|
32814
|
+
* @param {object} tileset
|
|
32815
|
+
* @param {number} tileCount - tiles in the tileset's atlas grid
|
|
32816
|
+
* (`atlasCols * atlasRows`)
|
|
32817
|
+
* @returns {{resource: BufferTextureResource, data: Uint8Array,
|
|
32818
|
+
* tileCount: number, dirty: boolean}|undefined}
|
|
32819
|
+
*/
|
|
32820
|
+
_getOrUpdateAnimLookup(tileset, tileCount) {
|
|
32821
|
+
if (!tileset.isAnimated || tileset.animations.size === 0) {
|
|
32822
|
+
return void 0;
|
|
31144
32823
|
}
|
|
31145
|
-
let
|
|
31146
|
-
if (
|
|
31147
|
-
|
|
31148
|
-
|
|
31149
|
-
|
|
31150
|
-
|
|
31151
|
-
|
|
31152
|
-
|
|
31153
|
-
|
|
31154
|
-
|
|
31155
|
-
|
|
31156
|
-
|
|
31157
|
-
|
|
32824
|
+
let entry = this.animLookups.get(tileset);
|
|
32825
|
+
if (entry === void 0) {
|
|
32826
|
+
const data3 = new Uint8Array(tileCount * 4);
|
|
32827
|
+
for (let id = 0; id < tileCount; id++) {
|
|
32828
|
+
data3[id * 4 + 0] = id & 255;
|
|
32829
|
+
data3[id * 4 + 1] = id >> 8 & 255;
|
|
32830
|
+
}
|
|
32831
|
+
const resource = new BufferTextureResource(data3, {
|
|
32832
|
+
width: tileCount,
|
|
32833
|
+
height: 1,
|
|
32834
|
+
premultipliedAlpha: false,
|
|
32835
|
+
repeat: "no-repeat",
|
|
32836
|
+
// NEAREST so the shader's `texelFetch` reads byte-exact
|
|
32837
|
+
// channel values back as normalized floats
|
|
32838
|
+
filter: this.gl.NEAREST,
|
|
32839
|
+
format: "rgba8"
|
|
32840
|
+
});
|
|
32841
|
+
entry = { resource, data: data3, tileCount, dirty: false };
|
|
32842
|
+
this.animLookups.set(tileset, entry);
|
|
32843
|
+
}
|
|
32844
|
+
const data2 = entry.data;
|
|
32845
|
+
for (const [localId, anim] of tileset.animations) {
|
|
32846
|
+
const off2 = localId * 4;
|
|
32847
|
+
const cur = anim.cur.tileid;
|
|
32848
|
+
const lo = cur & 255;
|
|
32849
|
+
const hi = cur >> 8 & 255;
|
|
32850
|
+
if (data2[off2] !== lo || data2[off2 + 1] !== hi) {
|
|
32851
|
+
data2[off2] = lo;
|
|
32852
|
+
data2[off2 + 1] = hi;
|
|
32853
|
+
entry.dirty = true;
|
|
31158
32854
|
}
|
|
31159
32855
|
}
|
|
31160
|
-
|
|
31161
|
-
|
|
31162
|
-
|
|
31163
|
-
|
|
31164
|
-
|
|
31165
|
-
|
|
31166
|
-
|
|
31167
|
-
|
|
31168
|
-
|
|
31169
|
-
|
|
32856
|
+
return entry;
|
|
32857
|
+
}
|
|
32858
|
+
/**
|
|
32859
|
+
* Get-or-create the per-layer index `BufferTextureResource`.
|
|
32860
|
+
* @param {TMXLayer} layer
|
|
32861
|
+
* @returns {BufferTextureResource}
|
|
32862
|
+
*/
|
|
32863
|
+
_getResource(layer) {
|
|
32864
|
+
let resource = this.resources.get(layer);
|
|
32865
|
+
if (resource === void 0) {
|
|
32866
|
+
resource = new BufferTextureResource(
|
|
32867
|
+
new Uint8Array(layer.layerData.buffer),
|
|
32868
|
+
{
|
|
32869
|
+
width: layer.cols,
|
|
32870
|
+
height: layer.rows,
|
|
32871
|
+
// raw GID bytes — must NOT have alpha pre-multiplied
|
|
32872
|
+
// into RGB, otherwise A=0 cells wipe their R/G/B
|
|
32873
|
+
premultipliedAlpha: false,
|
|
32874
|
+
repeat: "no-repeat",
|
|
32875
|
+
// NEAREST so `texelFetch` returns the original byte
|
|
32876
|
+
// values (as normalized floats) for the GID/flip
|
|
32877
|
+
// decode below
|
|
32878
|
+
filter: this.gl.NEAREST,
|
|
32879
|
+
format: "rgba8"
|
|
32880
|
+
}
|
|
32881
|
+
);
|
|
32882
|
+
resource._uploadedVersion = -1;
|
|
32883
|
+
this.resources.set(layer, resource);
|
|
32884
|
+
}
|
|
32885
|
+
return resource;
|
|
32886
|
+
}
|
|
32887
|
+
/**
|
|
32888
|
+
* Draw an orthogonal TMX layer through the shader path.
|
|
32889
|
+
* @param {TMXLayer} layer
|
|
32890
|
+
* @param {object} rect - the visible viewport rect (world coords)
|
|
32891
|
+
*/
|
|
32892
|
+
draw(layer, rect) {
|
|
32893
|
+
const renderer2 = this.renderer;
|
|
32894
|
+
const tileWidth = layer.tilewidth;
|
|
32895
|
+
const tileHeight = layer.tileheight;
|
|
32896
|
+
const cols = layer.cols;
|
|
32897
|
+
const rows = layer.rows;
|
|
32898
|
+
const startTileX = Math.max(0, Math.floor(rect.pos.x / tileWidth));
|
|
32899
|
+
const startTileY = Math.max(0, Math.floor(rect.pos.y / tileHeight));
|
|
32900
|
+
const endTileX = Math.min(
|
|
32901
|
+
cols,
|
|
32902
|
+
Math.ceil((rect.pos.x + rect.width) / tileWidth)
|
|
32903
|
+
);
|
|
32904
|
+
const endTileY = Math.min(
|
|
32905
|
+
rows,
|
|
32906
|
+
Math.ceil((rect.pos.y + rect.height) / tileHeight)
|
|
32907
|
+
);
|
|
32908
|
+
if (endTileX <= startTileX || endTileY <= startTileY) {
|
|
32909
|
+
return;
|
|
32910
|
+
}
|
|
32911
|
+
const worldX = startTileX * tileWidth;
|
|
32912
|
+
const worldY = startTileY * tileHeight;
|
|
32913
|
+
const worldW = (endTileX - startTileX) * tileWidth;
|
|
32914
|
+
const worldH = (endTileY - startTileY) * tileHeight;
|
|
32915
|
+
const visStartX = startTileX;
|
|
32916
|
+
const visStartY = startTileY;
|
|
32917
|
+
const visSizeX = endTileX - startTileX;
|
|
32918
|
+
const visSizeY = endTileY - startTileY;
|
|
32919
|
+
const batcher = renderer2.setBatcher("quad", this.shader);
|
|
32920
|
+
const resource = this._getResource(layer);
|
|
32921
|
+
const indexUnit = batcher.uploadTexture(
|
|
32922
|
+
resource,
|
|
32923
|
+
cols,
|
|
32924
|
+
rows,
|
|
32925
|
+
resource._uploadedVersion !== layer.dataVersion
|
|
32926
|
+
);
|
|
32927
|
+
resource._uploadedVersion = layer.dataVersion;
|
|
32928
|
+
this.shader.setUniform("uTileIndex", indexUnit);
|
|
32929
|
+
this._setV2("uMapSize", cols, rows);
|
|
32930
|
+
this._setV2("uVisibleStart", visStartX, visStartY);
|
|
32931
|
+
this._setV2("uVisibleSize", visSizeX, visSizeY);
|
|
32932
|
+
this._setV2("uCellSize", tileWidth, tileHeight);
|
|
32933
|
+
this.shader.setUniform("uOpacity", layer.getOpacity());
|
|
32934
|
+
this.shader.setUniform(
|
|
32935
|
+
"uTint",
|
|
32936
|
+
layer.tint ? layer.tint.toArray() : DEFAULT_TINT
|
|
32937
|
+
);
|
|
32938
|
+
const tilesets = layer.tilesets.tilesets;
|
|
32939
|
+
for (let i = 0; i < tilesets.length; i++) {
|
|
32940
|
+
const tileset = tilesets[i];
|
|
32941
|
+
if (tileset.isCollection || tileset.image === void 0) {
|
|
32942
|
+
continue;
|
|
32943
|
+
}
|
|
32944
|
+
const tsW = tileset.tilewidth;
|
|
32945
|
+
const tsH = tileset.tileheight;
|
|
32946
|
+
const margin = tileset.margin;
|
|
32947
|
+
const spacing = tileset.spacing;
|
|
32948
|
+
const atlasW = tileset.image.width;
|
|
32949
|
+
const atlasH = tileset.image.height;
|
|
32950
|
+
const atlasCols = Math.max(
|
|
32951
|
+
1,
|
|
32952
|
+
Math.floor((atlasW - margin * 2 + spacing) / (tsW + spacing))
|
|
32953
|
+
);
|
|
32954
|
+
const atlasRows = Math.max(
|
|
32955
|
+
1,
|
|
32956
|
+
Math.floor((atlasH - margin * 2 + spacing) / (tsH + spacing))
|
|
32957
|
+
);
|
|
32958
|
+
const tileCount = atlasCols * atlasRows;
|
|
32959
|
+
const animEntry = this._getOrUpdateAnimLookup(tileset, tileCount);
|
|
32960
|
+
if (animEntry !== void 0) {
|
|
32961
|
+
const animUnit = batcher.uploadTexture(
|
|
32962
|
+
animEntry.resource,
|
|
32963
|
+
animEntry.tileCount,
|
|
32964
|
+
1,
|
|
32965
|
+
animEntry.dirty
|
|
32966
|
+
);
|
|
32967
|
+
animEntry.dirty = false;
|
|
32968
|
+
this.shader.setUniform("uAnimLookup", animUnit);
|
|
32969
|
+
this.shader.setUniform("uAnimSize", animEntry.tileCount);
|
|
32970
|
+
} else {
|
|
32971
|
+
this.shader.setUniform("uAnimLookup", indexUnit);
|
|
32972
|
+
this.shader.setUniform("uAnimSize", 0);
|
|
32973
|
+
}
|
|
32974
|
+
this._setV2("uTileSize", tsW, tsH);
|
|
32975
|
+
this._setV2("uTilesetCols", atlasCols, atlasRows);
|
|
32976
|
+
this._setV2("uInvTilesetSize", 1 / atlasW, 1 / atlasH);
|
|
32977
|
+
this._setV4("uTilesetMargin", margin, margin, spacing, spacing);
|
|
32978
|
+
this._setV2(
|
|
32979
|
+
"uOverflow",
|
|
32980
|
+
Math.max(0, Math.ceil(tsW / tileWidth) - 1),
|
|
32981
|
+
Math.max(0, Math.ceil(tsH / tileHeight) - 1)
|
|
32982
|
+
);
|
|
32983
|
+
this._setV2("uGidRange", tileset.firstgid, tileset.lastgid);
|
|
32984
|
+
batcher.addQuad(
|
|
32985
|
+
tileset.texture,
|
|
32986
|
+
worldX,
|
|
32987
|
+
worldY,
|
|
32988
|
+
worldW,
|
|
32989
|
+
worldH,
|
|
32990
|
+
0,
|
|
32991
|
+
0,
|
|
32992
|
+
1,
|
|
32993
|
+
1,
|
|
32994
|
+
4294967295
|
|
32995
|
+
);
|
|
32996
|
+
batcher.flush();
|
|
31170
32997
|
}
|
|
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
32998
|
}
|
|
31177
32999
|
};
|
|
31178
33000
|
|
|
@@ -31205,6 +33027,7 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31205
33027
|
this._rectTriangles = Array.from({ length: 6 }, () => {
|
|
31206
33028
|
return { x: 0, y: 0 };
|
|
31207
33029
|
});
|
|
33030
|
+
this._clipAABB = new Bounds();
|
|
31208
33031
|
this._polyVerts = [];
|
|
31209
33032
|
this._currentGradient = null;
|
|
31210
33033
|
this.currentTransform = this.renderState.currentTransform;
|
|
@@ -31214,6 +33037,9 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31214
33037
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
31215
33038
|
const CustomBatcher = this.settings.batcher || this.settings.compositor;
|
|
31216
33039
|
this.addBatcher(new (CustomBatcher || QuadBatcher)(this), "quad", true);
|
|
33040
|
+
if (!CustomBatcher) {
|
|
33041
|
+
this.addBatcher(new LitQuadBatcher(this), "litQuad");
|
|
33042
|
+
}
|
|
31217
33043
|
this.addBatcher(new (CustomBatcher || PrimitiveBatcher)(this), "primitive");
|
|
31218
33044
|
this.addBatcher(new MeshBatcher(this), "mesh");
|
|
31219
33045
|
this.gl.disable(this.gl.DEPTH_TEST);
|
|
@@ -31314,6 +33140,7 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31314
33140
|
reset() {
|
|
31315
33141
|
super.reset();
|
|
31316
33142
|
this.clear();
|
|
33143
|
+
this._orthogonalTMXGPURenderer?.reset();
|
|
31317
33144
|
this.setViewport();
|
|
31318
33145
|
if (this.gl.getParameter(this.gl.ARRAY_BUFFER_BINDING) !== this.vertexBuffer) {
|
|
31319
33146
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
@@ -31332,6 +33159,63 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31332
33159
|
this.gl.disable(this.gl.SCISSOR_TEST);
|
|
31333
33160
|
this._scissorActive = false;
|
|
31334
33161
|
this._renderTargetPool.destroy();
|
|
33162
|
+
if (this._lightShader !== void 0) {
|
|
33163
|
+
this._lightShader.destroy?.();
|
|
33164
|
+
this._lightShader = void 0;
|
|
33165
|
+
}
|
|
33166
|
+
if (this._lightAtlas !== void 0) {
|
|
33167
|
+
this._lightAtlas.sources.forEach((source) => {
|
|
33168
|
+
this.cache.delete?.(source);
|
|
33169
|
+
});
|
|
33170
|
+
this._lightAtlas = void 0;
|
|
33171
|
+
}
|
|
33172
|
+
if (this.isContextValid === false) {
|
|
33173
|
+
this._orthogonalTMXGPURenderer = void 0;
|
|
33174
|
+
}
|
|
33175
|
+
}
|
|
33176
|
+
/**
|
|
33177
|
+
* Draw a TMX tile layer through whichever path the layer's `renderMode`
|
|
33178
|
+
* resolves to. WebGL2-eligible layers (`renderMode === "shader"`) take
|
|
33179
|
+
* the procedural shader path — one quad per tileset, GID lookup in a
|
|
33180
|
+
* per-layer data texture. All other layers fall through to the base
|
|
33181
|
+
* `Renderer.drawTileLayer` (preRender blit or per-tile loop).
|
|
33182
|
+
* @param {object} layer - the TMXLayer to draw
|
|
33183
|
+
* @param {object} rect - the visible region in world coords
|
|
33184
|
+
*/
|
|
33185
|
+
drawTileLayer(layer, rect) {
|
|
33186
|
+
if (layer.renderMode === "shader") {
|
|
33187
|
+
const gpu = this._getTMXGPURendererFor(layer.orientation);
|
|
33188
|
+
if (gpu !== void 0) {
|
|
33189
|
+
gpu.draw(layer, rect);
|
|
33190
|
+
return;
|
|
33191
|
+
}
|
|
33192
|
+
}
|
|
33193
|
+
super.drawTileLayer(layer, rect);
|
|
33194
|
+
}
|
|
33195
|
+
/**
|
|
33196
|
+
* Lazy-init the orientation-specific GPU tilemap renderer.
|
|
33197
|
+
* @param {string} orientation
|
|
33198
|
+
* @returns {object|undefined}
|
|
33199
|
+
* @ignore
|
|
33200
|
+
*/
|
|
33201
|
+
_getTMXGPURendererFor(orientation2) {
|
|
33202
|
+
if (orientation2 === "orthogonal") {
|
|
33203
|
+
if (this._orthogonalTMXGPURenderer === void 0) {
|
|
33204
|
+
try {
|
|
33205
|
+
this._orthogonalTMXGPURenderer = new OrthogonalTMXLayerGPURenderer(
|
|
33206
|
+
this
|
|
33207
|
+
);
|
|
33208
|
+
} catch (err) {
|
|
33209
|
+
console.warn(
|
|
33210
|
+
"melonJS: GPU tilemap shader failed to compile, falling back to legacy renderer",
|
|
33211
|
+
err
|
|
33212
|
+
);
|
|
33213
|
+
this._orthogonalTMXGPURenderer = null;
|
|
33214
|
+
}
|
|
33215
|
+
}
|
|
33216
|
+
return this._orthogonalTMXGPURenderer || void 0;
|
|
33217
|
+
}
|
|
33218
|
+
return void 0;
|
|
31335
33219
|
}
|
|
31336
33220
|
/**
|
|
31337
33221
|
* add a new batcher to this renderer
|
|
@@ -31359,21 +33243,21 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31359
33243
|
if (typeof batcher === "undefined") {
|
|
31360
33244
|
throw new Error("Invalid Batcher");
|
|
31361
33245
|
}
|
|
31362
|
-
|
|
33246
|
+
const targetShader = shader != null ? shader : batcher.defaultShader;
|
|
33247
|
+
if (this.currentBatcher === batcher && batcher.currentShader === targetShader) {
|
|
31363
33248
|
return this.currentBatcher;
|
|
31364
33249
|
}
|
|
31365
33250
|
if (this.currentBatcher !== batcher) {
|
|
31366
33251
|
if (this.currentBatcher !== void 0) {
|
|
31367
33252
|
this.currentBatcher.flush();
|
|
33253
|
+
this.currentBatcher.unbind();
|
|
31368
33254
|
}
|
|
31369
33255
|
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
|
|
31370
33256
|
this.currentBatcher = batcher;
|
|
31371
33257
|
this.currentBatcher.bind();
|
|
31372
33258
|
this.currentBatcher.setProjection(this.projectionMatrix);
|
|
31373
33259
|
}
|
|
31374
|
-
|
|
31375
|
-
this.currentBatcher.useShader(shader);
|
|
31376
|
-
}
|
|
33260
|
+
this.currentBatcher.useShader(targetShader);
|
|
31377
33261
|
return this.currentBatcher;
|
|
31378
33262
|
}
|
|
31379
33263
|
/**
|
|
@@ -31418,6 +33302,109 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31418
33302
|
flush() {
|
|
31419
33303
|
this.currentBatcher.flush();
|
|
31420
33304
|
}
|
|
33305
|
+
/**
|
|
33306
|
+
* Upload per-frame Light2d uniforms used by the lit sprite pipeline.
|
|
33307
|
+
*
|
|
33308
|
+
* Packs the active lights into pre-allocated scratch buffers, then
|
|
33309
|
+
* forwards to `LitQuadBatcher`. Light positions are translated from
|
|
33310
|
+
* world-space (where `light.getBounds().centerX/Y` lives) into the
|
|
33311
|
+
* renderer's pre-projection coords by subtracting `(translateX, translateY)`,
|
|
33312
|
+
* matching what `Stage.drawLighting` does for the cutout pass — so
|
|
33313
|
+
* the lit fragment's `lightPos - vWorldPos` math lines up with the
|
|
33314
|
+
* camera's view.
|
|
33315
|
+
*
|
|
33316
|
+
* Lights past `MAX_LIGHTS` (8) are silently dropped. Also caches the
|
|
33317
|
+
* active light count on the renderer so `drawImage` can dispatch
|
|
33318
|
+
* normal-mapped sprites to the lit batcher only when there's
|
|
33319
|
+
* something to light them with.
|
|
33320
|
+
* @param {Iterable<object>} [lights] - active `Light2d` instances; falsy/empty no-ops the lit pipeline
|
|
33321
|
+
* @param {object} [ambient] - ambient lighting color (0..255 RGB); defaults to black
|
|
33322
|
+
* @param {number} [translateX=0] - world-to-screen X translate (matches `Camera2d.draw()`)
|
|
33323
|
+
* @param {number} [translateY=0] - world-to-screen Y translate
|
|
33324
|
+
*/
|
|
33325
|
+
setLightUniforms(lights, ambient, translateX = 0, translateY = 0) {
|
|
33326
|
+
if (this._lightUniformsScratch === void 0) {
|
|
33327
|
+
this._lightUniformsScratch = createLightUniformScratch();
|
|
33328
|
+
}
|
|
33329
|
+
const u = packLights(
|
|
33330
|
+
lights,
|
|
33331
|
+
ambient,
|
|
33332
|
+
translateX,
|
|
33333
|
+
translateY,
|
|
33334
|
+
this._lightUniformsScratch
|
|
33335
|
+
);
|
|
33336
|
+
this.activeLightCount = u.count;
|
|
33337
|
+
const lit = this.batchers.get("litQuad");
|
|
33338
|
+
if (lit && typeof lit.setLightUniforms === "function") {
|
|
33339
|
+
lit.setLightUniforms(u);
|
|
33340
|
+
if (this.currentBatcher && this.currentBatcher !== lit) {
|
|
33341
|
+
const shader = this.currentBatcher.currentShader || this.currentBatcher.defaultShader;
|
|
33342
|
+
if (shader) {
|
|
33343
|
+
this.gl.useProgram(shader.program);
|
|
33344
|
+
this.currentProgram = shader.program;
|
|
33345
|
+
}
|
|
33346
|
+
}
|
|
33347
|
+
}
|
|
33348
|
+
}
|
|
33349
|
+
/**
|
|
33350
|
+
* @inheritdoc
|
|
33351
|
+
*
|
|
33352
|
+
* Renders the light as a quad through a shared
|
|
33353
|
+
* {@link RadialGradientEffect} fragment shader (procedural — no
|
|
33354
|
+
* per-light texture). The shader and a shared 1×1 white-pixel atlas
|
|
33355
|
+
* are lazy-allocated on first call and reused for every Light2d on
|
|
33356
|
+
* this renderer. Each light's color and intensity are encoded into
|
|
33357
|
+
* the per-vertex tint so consecutive `drawLight` calls accumulate
|
|
33358
|
+
* into the quad batcher's buffer and flush together — N lights
|
|
33359
|
+
* become 1 program switch + 1 flush instead of 2N + N.
|
|
33360
|
+
* @param {object} light - the Light2d instance to render
|
|
33361
|
+
*/
|
|
33362
|
+
drawLight(light) {
|
|
33363
|
+
if (this._lightShader === void 0) {
|
|
33364
|
+
this._lightShader = new RadialGradientEffect(this);
|
|
33365
|
+
}
|
|
33366
|
+
const batcher = this.setBatcher("quad", this._lightShader);
|
|
33367
|
+
batcher.addQuad(
|
|
33368
|
+
this._getLightAtlas(),
|
|
33369
|
+
light.pos.x,
|
|
33370
|
+
light.pos.y,
|
|
33371
|
+
light.width,
|
|
33372
|
+
light.height,
|
|
33373
|
+
0,
|
|
33374
|
+
0,
|
|
33375
|
+
1,
|
|
33376
|
+
1,
|
|
33377
|
+
// pack the light's color (RGB) and intensity (A) into the
|
|
33378
|
+
// vertex tint — the shader's `apply()` reads `color.rgb` and
|
|
33379
|
+
// `color.a` as the per-light values.
|
|
33380
|
+
light.color.toUint32(light.intensity)
|
|
33381
|
+
);
|
|
33382
|
+
}
|
|
33383
|
+
/**
|
|
33384
|
+
* Lazy-init a shared 1×1 white `TextureAtlas` used as the source
|
|
33385
|
+
* texture for `drawLight`'s procedural shader. The shader ignores
|
|
33386
|
+
* the sampled color, but `addQuad`'s vertex format includes a
|
|
33387
|
+
* texture-unit attribute so we still need a real texture; sharing
|
|
33388
|
+
* one across every light keeps them on the same multi-texture slot
|
|
33389
|
+
* (no flush on light switch).
|
|
33390
|
+
* @returns {TextureAtlas}
|
|
33391
|
+
* @ignore
|
|
33392
|
+
*/
|
|
33393
|
+
_getLightAtlas() {
|
|
33394
|
+
if (this._lightAtlas === void 0) {
|
|
33395
|
+
const canvas = globalThis.document ? globalThis.document.createElement("canvas") : new OffscreenCanvas(1, 1);
|
|
33396
|
+
canvas.width = 1;
|
|
33397
|
+
canvas.height = 1;
|
|
33398
|
+
const ctx = canvas.getContext("2d");
|
|
33399
|
+
ctx.fillStyle = "#fff";
|
|
33400
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
33401
|
+
this._lightAtlas = new TextureAtlas(
|
|
33402
|
+
createAtlas(1, 1, "lightWhite", "no-repeat"),
|
|
33403
|
+
canvas
|
|
33404
|
+
);
|
|
33405
|
+
}
|
|
33406
|
+
return this._lightAtlas;
|
|
33407
|
+
}
|
|
31421
33408
|
/**
|
|
31422
33409
|
* Begin capturing rendering to an offscreen FBO for post-effect processing.
|
|
31423
33410
|
* @param {Renderable} renderable - the renderable requesting post-effect processing
|
|
@@ -31602,19 +33589,22 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31602
33589
|
*/
|
|
31603
33590
|
enableScissor(x, y, width, height) {
|
|
31604
33591
|
const gl = this.gl;
|
|
33592
|
+
const canvas = this.getCanvas();
|
|
33593
|
+
const aabb = this._clipAABB;
|
|
33594
|
+
aabb.clear();
|
|
33595
|
+
aabb.addFrame(x, y, x + width, y + height, this.currentTransform);
|
|
33596
|
+
const sx = Math.floor(aabb.min.x);
|
|
33597
|
+
const sy = Math.floor(aabb.min.y);
|
|
33598
|
+
const sw = Math.ceil(aabb.max.x - sx);
|
|
33599
|
+
const sh = Math.ceil(aabb.max.y - sy);
|
|
31605
33600
|
this.flush();
|
|
31606
33601
|
gl.enable(gl.SCISSOR_TEST);
|
|
31607
33602
|
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;
|
|
33603
|
+
gl.scissor(sx, canvas.height - sh - sy, sw, sh);
|
|
33604
|
+
this.currentScissor[0] = sx;
|
|
33605
|
+
this.currentScissor[1] = sy;
|
|
33606
|
+
this.currentScissor[2] = sw;
|
|
33607
|
+
this.currentScissor[3] = sh;
|
|
31618
33608
|
}
|
|
31619
33609
|
/**
|
|
31620
33610
|
* Disable the scissor test, allowing rendering to the full viewport.
|
|
@@ -31721,27 +33711,45 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31721
33711
|
dx |= 0;
|
|
31722
33712
|
dy |= 0;
|
|
31723
33713
|
}
|
|
31724
|
-
this.
|
|
33714
|
+
const useLit = this.batchers.has("litQuad") && this.activeLightCount > 0 && this.currentNormalMap !== null;
|
|
33715
|
+
this.setBatcher(useLit ? "litQuad" : "quad");
|
|
31725
33716
|
const shader = this.customShader;
|
|
31726
|
-
if (
|
|
33717
|
+
if (shader != null) {
|
|
31727
33718
|
this.currentBatcher.useShader(shader);
|
|
31728
33719
|
}
|
|
31729
33720
|
const reupload = typeof image.videoWidth !== "undefined";
|
|
31730
33721
|
const texture = this.cache.get(image);
|
|
31731
33722
|
const uvs = texture.getUVs(sx, sy, sw, sh);
|
|
31732
|
-
|
|
31733
|
-
|
|
31734
|
-
|
|
31735
|
-
|
|
31736
|
-
|
|
31737
|
-
|
|
31738
|
-
|
|
31739
|
-
|
|
31740
|
-
|
|
31741
|
-
|
|
31742
|
-
|
|
31743
|
-
|
|
31744
|
-
|
|
33723
|
+
if (useLit) {
|
|
33724
|
+
this.currentBatcher.addQuad(
|
|
33725
|
+
texture,
|
|
33726
|
+
dx,
|
|
33727
|
+
dy,
|
|
33728
|
+
dw,
|
|
33729
|
+
dh,
|
|
33730
|
+
uvs[0],
|
|
33731
|
+
uvs[1],
|
|
33732
|
+
uvs[2],
|
|
33733
|
+
uvs[3],
|
|
33734
|
+
this.currentTint.toUint32(this.getGlobalAlpha()),
|
|
33735
|
+
reupload,
|
|
33736
|
+
this.currentNormalMap
|
|
33737
|
+
);
|
|
33738
|
+
} else {
|
|
33739
|
+
this.currentBatcher.addQuad(
|
|
33740
|
+
texture,
|
|
33741
|
+
dx,
|
|
33742
|
+
dy,
|
|
33743
|
+
dw,
|
|
33744
|
+
dh,
|
|
33745
|
+
uvs[0],
|
|
33746
|
+
uvs[1],
|
|
33747
|
+
uvs[2],
|
|
33748
|
+
uvs[3],
|
|
33749
|
+
this.currentTint.toUint32(this.getGlobalAlpha()),
|
|
33750
|
+
reupload
|
|
33751
|
+
);
|
|
33752
|
+
}
|
|
31745
33753
|
if (typeof shader === "object") {
|
|
31746
33754
|
this.currentBatcher.useShader(this.currentBatcher.defaultShader);
|
|
31747
33755
|
}
|
|
@@ -31781,7 +33789,7 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31781
33789
|
drawMesh(mesh) {
|
|
31782
33790
|
const gl = this.gl;
|
|
31783
33791
|
this.setBatcher("mesh");
|
|
31784
|
-
if (
|
|
33792
|
+
if (this.customShader != null) {
|
|
31785
33793
|
this.currentBatcher.useShader(this.customShader);
|
|
31786
33794
|
}
|
|
31787
33795
|
gl.enable(gl.DEPTH_TEST);
|
|
@@ -31806,7 +33814,7 @@ var WebGLRenderer = class extends Renderer {
|
|
|
31806
33814
|
gl.enable(gl.BLEND);
|
|
31807
33815
|
gl.disable(gl.DEPTH_TEST);
|
|
31808
33816
|
gl.depthMask(false);
|
|
31809
|
-
if (
|
|
33817
|
+
if (this.customShader != null) {
|
|
31810
33818
|
this.currentBatcher.useShader(this.currentBatcher.defaultShader);
|
|
31811
33819
|
}
|
|
31812
33820
|
}
|
|
@@ -32041,23 +34049,33 @@ var WebGLRenderer = class extends Renderer {
|
|
|
32041
34049
|
*/
|
|
32042
34050
|
restore() {
|
|
32043
34051
|
const canvas = this.getCanvas();
|
|
34052
|
+
const peek = this.renderState.peekScissor();
|
|
34053
|
+
const cur = this.currentScissor;
|
|
34054
|
+
const curActive = this._scissorActive === true;
|
|
34055
|
+
const willBeActive = peek !== null;
|
|
34056
|
+
const scissorChanging = curActive !== willBeActive || willBeActive && (cur[0] !== peek[0] || cur[1] !== peek[1] || cur[2] !== peek[2] || cur[3] !== peek[3]);
|
|
34057
|
+
if (scissorChanging) {
|
|
34058
|
+
this.flush();
|
|
34059
|
+
}
|
|
32044
34060
|
const result = this.renderState.restore(canvas.width, canvas.height);
|
|
32045
34061
|
if (result !== null) {
|
|
32046
34062
|
this.setBlendMode(result.blendMode);
|
|
32047
|
-
if (
|
|
34063
|
+
if (scissorChanging) {
|
|
32048
34064
|
const gl = this.gl;
|
|
32049
|
-
|
|
32050
|
-
|
|
32051
|
-
|
|
32052
|
-
|
|
32053
|
-
|
|
32054
|
-
|
|
32055
|
-
|
|
32056
|
-
|
|
32057
|
-
|
|
32058
|
-
|
|
32059
|
-
|
|
32060
|
-
|
|
34065
|
+
if (result.scissorActive) {
|
|
34066
|
+
const next = this.currentScissor;
|
|
34067
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
34068
|
+
this._scissorActive = true;
|
|
34069
|
+
gl.scissor(
|
|
34070
|
+
next[0],
|
|
34071
|
+
canvas.height - next[3] - next[1],
|
|
34072
|
+
next[2],
|
|
34073
|
+
next[3]
|
|
34074
|
+
);
|
|
34075
|
+
} else {
|
|
34076
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
34077
|
+
this._scissorActive = false;
|
|
34078
|
+
}
|
|
32061
34079
|
}
|
|
32062
34080
|
}
|
|
32063
34081
|
this._currentGradient = this.renderState.currentGradient;
|
|
@@ -32726,31 +34744,42 @@ var WebGLRenderer = class extends Renderer {
|
|
|
32726
34744
|
clipRect(x, y, width, height) {
|
|
32727
34745
|
const canvas = this.getCanvas();
|
|
32728
34746
|
const gl = this.gl;
|
|
32729
|
-
|
|
32730
|
-
|
|
34747
|
+
const m = this.currentTransform;
|
|
34748
|
+
if (!Number.isFinite(m.tx) || !Number.isFinite(m.ty)) {
|
|
32731
34749
|
if (this._scissorActive) {
|
|
32732
|
-
|
|
32733
|
-
|
|
32734
|
-
|
|
34750
|
+
this.flush();
|
|
34751
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
34752
|
+
this._scissorActive = false;
|
|
32735
34753
|
}
|
|
32736
|
-
|
|
32737
|
-
|
|
32738
|
-
|
|
32739
|
-
|
|
32740
|
-
|
|
32741
|
-
|
|
32742
|
-
|
|
32743
|
-
|
|
32744
|
-
|
|
32745
|
-
|
|
32746
|
-
|
|
32747
|
-
|
|
32748
|
-
|
|
32749
|
-
|
|
32750
|
-
|
|
32751
|
-
|
|
32752
|
-
|
|
34754
|
+
return;
|
|
34755
|
+
}
|
|
34756
|
+
const aabb = this._clipAABB;
|
|
34757
|
+
aabb.clear();
|
|
34758
|
+
aabb.addFrame(x, y, x + width, y + height, m);
|
|
34759
|
+
const sx = Math.floor(aabb.min.x);
|
|
34760
|
+
const sy = Math.floor(aabb.min.y);
|
|
34761
|
+
const sw = Math.ceil(aabb.max.x - sx);
|
|
34762
|
+
const sh = Math.ceil(aabb.max.y - sy);
|
|
34763
|
+
if (sx <= 0 && sy <= 0 && sx + sw >= canvas.width && sy + sh >= canvas.height) {
|
|
34764
|
+
if (this._scissorActive) {
|
|
34765
|
+
this.flush();
|
|
34766
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
34767
|
+
this._scissorActive = false;
|
|
34768
|
+
}
|
|
34769
|
+
return;
|
|
34770
|
+
}
|
|
34771
|
+
const cs = this.currentScissor;
|
|
34772
|
+
if (this._scissorActive && cs[0] === sx && cs[1] === sy && cs[2] === sw && cs[3] === sh) {
|
|
34773
|
+
return;
|
|
32753
34774
|
}
|
|
34775
|
+
this.flush();
|
|
34776
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
34777
|
+
this._scissorActive = true;
|
|
34778
|
+
gl.scissor(sx, canvas.height - sh - sy, sw, sh);
|
|
34779
|
+
cs[0] = sx;
|
|
34780
|
+
cs[1] = sy;
|
|
34781
|
+
cs[2] = sw;
|
|
34782
|
+
cs[3] = sh;
|
|
32754
34783
|
}
|
|
32755
34784
|
/**
|
|
32756
34785
|
* A mask limits rendering elements to the shape and position of the given mask object.
|
|
@@ -32769,15 +34798,16 @@ var WebGLRenderer = class extends Renderer {
|
|
|
32769
34798
|
}
|
|
32770
34799
|
this.maskLevel++;
|
|
32771
34800
|
gl.colorMask(false, false, false, false);
|
|
32772
|
-
gl.
|
|
32773
|
-
gl.
|
|
34801
|
+
gl.stencilMask(255);
|
|
34802
|
+
gl.stencilFunc(gl.ALWAYS, 0, 255);
|
|
34803
|
+
gl.stencilOp(gl.KEEP, gl.KEEP, gl.INCR);
|
|
32774
34804
|
this.fill(mask);
|
|
32775
34805
|
this.flush();
|
|
32776
34806
|
gl.colorMask(true, true, true, true);
|
|
32777
34807
|
if (invert === true) {
|
|
32778
|
-
gl.stencilFunc(gl.EQUAL,
|
|
34808
|
+
gl.stencilFunc(gl.EQUAL, 0, 255);
|
|
32779
34809
|
} else {
|
|
32780
|
-
gl.stencilFunc(gl.
|
|
34810
|
+
gl.stencilFunc(gl.EQUAL, this.maskLevel, 255);
|
|
32781
34811
|
}
|
|
32782
34812
|
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
|
|
32783
34813
|
}
|
|
@@ -33108,6 +35138,14 @@ var Application = class {
|
|
|
33108
35138
|
this.world = new World(0, 0, this.settings.width, this.settings.height);
|
|
33109
35139
|
this.world.app = this;
|
|
33110
35140
|
this.world.physic = this.settings.physic;
|
|
35141
|
+
this.world.gpuTilemap = this.settings.gpuTilemap;
|
|
35142
|
+
if (this.settings.gpuTilemap && // duck-type rather than `instanceof WebGLRenderer` to avoid a
|
|
35143
|
+
// runtime import; only the WebGL renderer carries `WebGLVersion`
|
|
35144
|
+
this.renderer.WebGLVersion !== 2) {
|
|
35145
|
+
console.warn(
|
|
35146
|
+
"melonJS: gpuTilemap is enabled but the active renderer is not WebGL 2 \u2014 falling back to the legacy tile renderer for every tile layer"
|
|
35147
|
+
);
|
|
35148
|
+
}
|
|
33111
35149
|
this.lastUpdate = globalThis.performance.now();
|
|
33112
35150
|
if (!this.isInitialized) {
|
|
33113
35151
|
on(STATE_CHANGE, this.repaint, this);
|
|
@@ -33129,9 +35167,7 @@ var Application = class {
|
|
|
33129
35167
|
reset() {
|
|
33130
35168
|
const current = state_default.get();
|
|
33131
35169
|
if (typeof current !== "undefined") {
|
|
33132
|
-
this.viewport = current.cameras.get(
|
|
33133
|
-
"default"
|
|
33134
|
-
);
|
|
35170
|
+
this.viewport = current.cameras.get("default");
|
|
33135
35171
|
}
|
|
33136
35172
|
emit(GAME_RESET);
|
|
33137
35173
|
this.updateFrameRate();
|