@woosh/meep-engine 2.166.0 → 2.167.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/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Pure JavaScript game engine. Fully featured and production ready.",
7
7
  "type": "module",
8
8
  "author": "Alexander Goldring",
9
- "version": "2.166.0",
9
+ "version": "2.167.0",
10
10
  "main": "build/meep.module.js",
11
11
  "module": "build/meep.module.js",
12
12
  "exports": {
@@ -28,6 +28,18 @@ export class FogOfWar {
28
28
  */
29
29
  size: Vector2;
30
30
  color: Vector4;
31
+ /**
32
+ * What the fog does outside the fogged area — see {@link FogOfWarEdgeMode}.
33
+ *
34
+ * NOT serialized: it is configuration the owner sets when it builds the
35
+ * component, not accumulated reveal state, and adding it to the binary format
36
+ * would cost a version bump plus an updater for every existing save. A
37
+ * deserialized FogOfWar therefore comes back as Conceal; a consumer that
38
+ * wants Extend sets it after loading, in the same place it sets {@link color}.
39
+ *
40
+ * @type {FogOfWarEdgeMode}
41
+ */
42
+ edge_mode: FogOfWarEdgeMode;
31
43
  /**
32
44
  * Contains indices of pixes that are currently being updated
33
45
  * @type {RowFirstTable}
@@ -134,6 +146,7 @@ import { DataTexture } from "three";
134
146
  import Vector1 from "../../../core/geom/Vector1.js";
135
147
  import Vector2 from "../../../core/geom/Vector2.js";
136
148
  import Vector4 from "../../../core/geom/Vector4.js";
149
+ import { FogOfWarEdgeMode } from "./FogOfWarEdgeMode.js";
137
150
  import { RowFirstTable } from "../../../core/collection/table/RowFirstTable.js";
138
151
  import Signal from "../../../core/events/signal/Signal.js";
139
152
  import { Sampler2D } from "../../graphics/texture/sampler/Sampler2D.js";
@@ -1 +1 @@
1
- {"version":3,"file":"FogOfWar.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/fow/FogOfWar.js"],"names":[],"mappings":"AA+CA;;GAEG;AACH;IAEI;;;OAGG;IACH,oBAFU,OAAO,CAES;IAC1B;;;OAGG;IACH,0BAFU,OAAO,CAEgB;IAEjC;;;OAGG;IACH,SAFU,WAAW,GAAC,IAAI,CAEX;IAEf;;;OAGG;IACH,OAFU,OAAO,CAEM;IAEvB;;;OAGG;IACH,MAFU,OAAO,CAEQ;IAEzB,eAAsC;IAEtC;;;OAGG;IACH,UAFU,aAAa,CAE2B;IAWlD;;MAEE;IAEF;;;OAGG;IACH,SAFU,SAAS,CAEgB;IAEnC;;;OAGG;IACH,iBAFU,SAAS,CAEwB;IAE3C;;;OAGG;IACH,sBAFW,MAAM,QA6EhB;IA4DD;;;;OAIG;IACH,eAHW,MAAM,KACN,MAAM,QAUhB;IAED,cAQC;IAED,kBAQC;IAED,mBAEC;IAED;;;;;OAKG;IACH,UAJW,MAAM,KACN,MAAM,UACN,MAAM,QAmChB;IAED;;;;;OAKG;IACH,WAJW,MAAM,KACN,MAAM,UACN,MAAM,QAmChB;IAED;;;;;;OAMG;IACH,yBAcC;IAED,gBAIC;IAED,sBAqCC;IAED;;;OAGG;IACH,kBAFW,MAAM,QAYhB;IAED;;;OAGG;IACH,0DA+BC;IAED;;;;;OAKG;IACH,yBAJW,MAAM,KACN,MAAM,UACN,MAAM,EAAE,QAelB;IAED;;;;;OAKG;IACH,yEAFW,MAAM,WA+EhB;IAED;;;;;OAKG;IACH,qBAJW,MAAM,KACN,MAAM,GACJ,MAAM,CASlB;IAED,6BAKC;IAED,+BASC;IAED,uBAyBC;IAGD;;;;;OAKG;IACH,UAJW,MAAM,KACN,MAAM,oBAahB;;CACJ;;;;4BAvqB2F,OAAO;oBAM/E,+BAA+B;oBAC/B,+BAA+B;oBAC/B,+BAA+B;8BALrB,iDAAiD;mBAE5D,uCAAuC;0BAQhC,6CAA6C"}
1
+ {"version":3,"file":"FogOfWar.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/fow/FogOfWar.js"],"names":[],"mappings":"AAgDA;;GAEG;AACH;IAEI;;;OAGG;IACH,oBAFU,OAAO,CAES;IAC1B;;;OAGG;IACH,0BAFU,OAAO,CAEgB;IAEjC;;;OAGG;IACH,SAFU,WAAW,GAAC,IAAI,CAEX;IAEf;;;OAGG;IACH,OAFU,OAAO,CAEM;IAEvB;;;OAGG;IACH,MAFU,OAAO,CAEQ;IAEzB,eAAsC;IAEtC;;;;;;;;;;OAUG;IACH,WAFU,gBAAgB,CAEW;IAErC;;;OAGG;IACH,UAFU,aAAa,CAE2B;IAWlD;;MAEE;IAEF;;;OAGG;IACH,SAFU,SAAS,CAEgB;IAEnC;;;OAGG;IACH,iBAFU,SAAS,CAEwB;IAE3C;;;OAGG;IACH,sBAFW,MAAM,QA6EhB;IA4DD;;;;OAIG;IACH,eAHW,MAAM,KACN,MAAM,QAUhB;IAED,cAQC;IAED,kBAQC;IAED,mBAEC;IAED;;;;;OAKG;IACH,UAJW,MAAM,KACN,MAAM,UACN,MAAM,QAmChB;IAED;;;;;OAKG;IACH,WAJW,MAAM,KACN,MAAM,UACN,MAAM,QAmChB;IAED;;;;;;OAMG;IACH,yBAcC;IAED,gBAIC;IAmDD,sBA2CC;IAED;;;OAGG;IACH,kBAFW,MAAM,QAYhB;IAED;;;OAGG;IACH,0DA+BC;IAED;;;;;OAKG;IACH,yBAJW,MAAM,KACN,MAAM,UACN,MAAM,EAAE,QAelB;IAED;;;;;OAKG;IACH,yEAFW,MAAM,WA+EhB;IAED;;;;;OAKG;IACH,qBAJW,MAAM,KACN,MAAM,GACJ,MAAM,CASlB;IAED,6BAKC;IAED,+BASC;IAED,uBAyBC;IAGD;;;;;OAKG;IACH,UAJW,MAAM,KACN,MAAM,oBAahB;;CACJ;;;;4BA5uB2F,OAAO;oBAM/E,+BAA+B;oBAC/B,+BAA+B;oBAC/B,+BAA+B;iCAOlB,uBAAuB;8BAZ1B,iDAAiD;mBAE5D,uCAAuC;0BAQhC,6CAA6C"}
@@ -13,6 +13,7 @@ import { min2 } from "../../../core/math/min2.js";
13
13
  import { computeUnsignedDistanceField } from "../../graphics/texture/sampler/distance/computeUnsignedDistanceField.js";
14
14
  import { Sampler2D } from "../../graphics/texture/sampler/Sampler2D.js";
15
15
  import { writeSample2DDataToDataTexture } from "../../graphics/texture/sampler/writeSampler2DDataToDataTexture.js";
16
+ import { FogOfWarEdgeMode } from "./FogOfWarEdgeMode.js";
16
17
 
17
18
  const samplePosition = [];
18
19
 
@@ -81,6 +82,19 @@ export class FogOfWar {
81
82
 
82
83
  color = new Vector4(0.1, 0.1, 0.1, 1);
83
84
 
85
+ /**
86
+ * What the fog does outside the fogged area — see {@link FogOfWarEdgeMode}.
87
+ *
88
+ * NOT serialized: it is configuration the owner sets when it builds the
89
+ * component, not accumulated reveal state, and adding it to the binary format
90
+ * would cost a version bump plus an updater for every existing save. A
91
+ * deserialized FogOfWar therefore comes back as Conceal; a consumer that
92
+ * wants Extend sets it after loading, in the same place it sets {@link color}.
93
+ *
94
+ * @type {FogOfWarEdgeMode}
95
+ */
96
+ edge_mode = FogOfWarEdgeMode.Conceal;
97
+
84
98
  /**
85
99
  * Contains indices of pixes that are currently being updated
86
100
  * @type {RowFirstTable}
@@ -401,7 +415,62 @@ export class FogOfWar {
401
415
  }
402
416
  }
403
417
 
418
+ /**
419
+ * Copy the sampler's outermost REAL cells out into the 1-cell ring that
420
+ * {@link rebuildSampler} pads it with.
421
+ *
422
+ * That ring is padding — it exists so the shader's 5x5 blur and the distance
423
+ * field have something to read past the edge — and it is filled with full
424
+ * concealment. The blur then drags that concealment ~2 cells back INTO the
425
+ * fogged area, so the outermost cells of the world can never read as fully
426
+ * revealed however thoroughly they are explored; and every lookup from outside
427
+ * the area clamps onto it, which is what paints the void. Mirroring makes the
428
+ * padding agree with the edge it pads, on both counts.
429
+ *
430
+ * O(perimeter), and only on frames that upload a new texture.
431
+ *
432
+ * @private
433
+ */
434
+ #mirrorBorderRing() {
435
+ const s = this.sampler;
436
+
437
+ const w = s.width;
438
+ const h = s.height;
439
+
440
+ if (w < 3 || h < 3) {
441
+ // no interior to mirror from
442
+ return;
443
+ }
444
+
445
+ const data = s.data;
446
+
447
+ const last_row = (h - 1) * w;
448
+ const second_last_row = (h - 2) * w;
449
+
450
+ // top and bottom edges, corners excluded — those come from the sides below
451
+ for (let x = 1; x < w - 1; x++) {
452
+ data[x] = data[w + x];
453
+ data[last_row + x] = data[second_last_row + x];
454
+ }
455
+
456
+ // left and right edges, corners INCLUDED: the row above and below have
457
+ // already been mirrored, so a corner ends up with its diagonal neighbour's
458
+ // value rather than an arbitrary one
459
+ for (let y = 0; y < h; y++) {
460
+ const row = y * w;
461
+
462
+ data[row] = data[row + 1];
463
+ data[row + w - 1] = data[row + w - 2];
464
+ }
465
+ }
466
+
404
467
  updateTexture() {
468
+ if (this.edge_mode === FogOfWarEdgeMode.Extend) {
469
+ // do this HERE, at the single point every reveal path funnels through
470
+ // on its way to the GPU, rather than in each of them
471
+ this.#mirrorBorderRing();
472
+ }
473
+
405
474
  const s = this.sampler;
406
475
 
407
476
  /**
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What the fog of war does AT, and beyond, the edge of the fogged area.
3
+ *
4
+ * The fog is a screen-space effect (see screenSpaceFogOfWarShader): per pixel it
5
+ * reconstructs a world position from the depth buffer and reads that position out
6
+ * of the fog sampler. Neither end of that pipeline is defined outside the fogged
7
+ * rectangle — a pixel with no geometry has no world position to reconstruct, and
8
+ * a world position outside the sampler has no cell to read — so what happens
9
+ * there is a POLICY rather than a fact, and this is the knob for it.
10
+ */
11
+ export type FogOfWarEdgeMode = number;
12
+ export namespace FogOfWarEdgeMode {
13
+ let Conceal: number;
14
+ let Extend: number;
15
+ }
16
+ //# sourceMappingURL=FogOfWarEdgeMode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FogOfWarEdgeMode.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/fow/FogOfWarEdgeMode.js"],"names":[],"mappings":";;;;;;;;;;+BAUU,MAAM"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * What the fog of war does AT, and beyond, the edge of the fogged area.
3
+ *
4
+ * The fog is a screen-space effect (see screenSpaceFogOfWarShader): per pixel it
5
+ * reconstructs a world position from the depth buffer and reads that position out
6
+ * of the fog sampler. Neither end of that pipeline is defined outside the fogged
7
+ * rectangle — a pixel with no geometry has no world position to reconstruct, and
8
+ * a world position outside the sampler has no cell to read — so what happens
9
+ * there is a POLICY rather than a fact, and this is the knob for it.
10
+ *
11
+ * @enum {number}
12
+ */
13
+ export const FogOfWarEdgeMode = {
14
+ /**
15
+ * Everything outside the fogged area is CONCEALED: the sampler's border ring
16
+ * is left at full concealment, and a pixel with no geometry takes whatever
17
+ * the clamped lookup returns — which is that same border.
18
+ *
19
+ * The default, and the behaviour of every release before this option existed.
20
+ * Right for a world that fills the screen, where "outside" is somewhere the
21
+ * player is never shown.
22
+ */
23
+ Conceal: 0,
24
+
25
+ /**
26
+ * The fog EXTENDS outward instead: the border ring mirrors the cells just
27
+ * inside it, and pixels with no geometry are left alone entirely.
28
+ *
29
+ * For a world with a VISIBLE boundary — a finite arena the camera can see
30
+ * past. Under {@link FogOfWarEdgeMode.Conceal} such a scene gets a hard black
31
+ * rectangle drawn exactly on its boundary (the concealed border ring, clamped
32
+ * across the whole void), which both advertises where the map stops and paints
33
+ * over anything drawn earlier to hide it — this layer composites last, over
34
+ * every other effect. Under Extend the fog agrees with the ground it borders:
35
+ * explored terrain stays clear right out past the boundary, unexplored terrain
36
+ * stays dark, and nothing marks where the geometry ends.
37
+ */
38
+ Extend: 1,
39
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"FogOfWarSystem.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/fow/FogOfWarSystem.js"],"names":[],"mappings":"AAwBA;IACI;;;OAGG;IACH,sCA0BC;IAvBG;;;OAGG;IACH,yBAAwB;IAExB,mDAAuC;IAEvC,gEAEC;IAED,mCAAoD;IAIpD;;;OAGG;IACH,eAFU,aAAa,CAEE;IAEzB,uBAAuB;IAG3B;;;OAGG;IACH,yBAFa,gBAAgB,CAuE5B;IAsBG,kBAAgC;IA+DpC,4CAEC;IAED;;;;;OAKG;IACH,UAJW,QAAQ,WACR,OAAO,UACP,MAAM,QAmBhB;IAED,kDAQC;IAED,6BAUC;CACJ;uBAzPsB,cAAc;oBACjB,2BAA2B;yBACtB,eAAe;4CAdI,oDAAoD;iCAS/D,sDAAsD;8BAPzD,0CAA0C"}
1
+ {"version":3,"file":"FogOfWarSystem.d.ts","sourceRoot":"","sources":["../../../../../src/engine/ecs/fow/FogOfWarSystem.js"],"names":[],"mappings":"AAwBA;IACI;;;OAGG;IACH,sCA0BC;IAvBG;;;OAGG;IACH,yBAAwB;IAExB,mDAAuC;IAEvC,gEAEC;IAED,mCAAoD;IAIpD;;;OAGG;IACH,eAFU,aAAa,CAEE;IAEzB,uBAAuB;IAG3B;;;OAGG;IACH,yBAFa,gBAAgB,CAuE5B;IAsBG,kBAAgC;IAgEpC,4CAEC;IAED;;;;;OAKG;IACH,UAJW,QAAQ,WACR,OAAO,UACP,MAAM,QAmBhB;IAED,kDAQC;IAED,6BAUC;CACJ;uBA1PsB,cAAc;oBACjB,2BAA2B;yBACtB,eAAe;4CAdI,oDAAoD;iCAS/D,sDAAsD;8BAPzD,0CAA0C"}
@@ -178,6 +178,7 @@ export class FogOfWarSystem extends System {
178
178
  fogOfWarRenderer.setResolution(fow.size.x, fow.size.y);
179
179
  fogOfWarRenderer.setUvTransformFromFog(fow);
180
180
  fogOfWarRenderer.setFogColor(fow.color);
181
+ fogOfWarRenderer.setEdgeMode(fow.edge_mode);
181
182
 
182
183
 
183
184
  //stop traversal
@@ -47,6 +47,12 @@ export class FogOfWarRenderer {
47
47
  * @param {Vector4} color
48
48
  */
49
49
  setFogColor(color: Vector4): void;
50
+ /**
51
+ * What the fog does outside the fogged area. See {@link FogOfWarEdgeMode}.
52
+ *
53
+ * @param {FogOfWarEdgeMode} mode
54
+ */
55
+ setEdgeMode(mode: FogOfWarEdgeMode): void;
50
56
  /**
51
57
  *
52
58
  * @param {WebGLRenderer} renderer
@@ -59,4 +65,5 @@ export class FogOfWarRenderer {
59
65
  import { Mesh } from "three";
60
66
  import { OrthographicCamera } from "three";
61
67
  import { Scene } from "three";
68
+ import { FogOfWarEdgeMode } from "../FogOfWarEdgeMode.js";
62
69
  //# sourceMappingURL=FogOfWarRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"FogOfWarRenderer.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/fow/shader/FogOfWarRenderer.js"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,+EAFoB,MAAM,QAAE,MAAM,QAAE,MAAM,QAAE,MAAM,gBAiBjD;AAED;IAEQ;;;OAGG;IACH,yBAAgD;IAEhD,2DAAkE;IAElE,2BAAwD;IACxD,aAAwB;IAI5B;;;OAGG;IACH,2CAKC;IAED;;;;;;OAMG;IACH,wBALW,MAAM,WACN,MAAM,UACN,MAAM,UACN,MAAM,QAqBhB;IAED;;;OAGG;IACH,uCAIC;IAED;;;OAGG;IACH,oCAGC;IAED;;;;OAIG;IACH,iBAHW,MAAM,KACN,MAAM,QAIhB;IAED;;;OAGG;IACH,kCAEC;IAED;;;;;;OAMG;IACH,wCAJW,oBAAkB,kBAAkB,SACpC,KAAK,mCAqBf;CACJ;qBAhJ+C,OAAO;mCAAP,OAAO;sBAAP,OAAO"}
1
+ {"version":3,"file":"FogOfWarRenderer.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/fow/shader/FogOfWarRenderer.js"],"names":[],"mappings":"AAOA;;;;GAIG;AACH,+EAFoB,MAAM,QAAE,MAAM,QAAE,MAAM,QAAE,MAAM,gBAiBjD;AAED;IAEQ;;;OAGG;IACH,yBAAgD;IAEhD,2DAAkE;IAElE,2BAAwD;IACxD,aAAwB;IAI5B;;;OAGG;IACH,2CAKC;IAED;;;;;;OAMG;IACH,wBALW,MAAM,WACN,MAAM,UACN,MAAM,UACN,MAAM,QAqBhB;IAED;;;OAGG;IACH,uCAIC;IAED;;;OAGG;IACH,oCAGC;IAED;;;;OAIG;IACH,iBAHW,MAAM,KACN,MAAM,QAIhB;IAED;;;OAGG;IACH,kCAEC;IAED;;;;OAIG;IACH,kBAFW,gBAAgB,QAI1B;IAED;;;;;;OAMG;IACH,wCAJW,oBAAkB,kBAAkB,SACpC,KAAK,mCAqBf;CACJ;qBA1J+C,OAAO;mCAAP,OAAO;sBAAP,OAAO;iCAItB,wBAAwB"}
@@ -1,145 +1,155 @@
1
- import { Mesh, OrthographicCamera, Scene } from "three";
2
- import { assert } from "../../../../core/assert.js";
3
- import { isValueBetweenInclusive } from "../../../../core/math/interval/isValueBetweenInclusive.js";
4
- import { FULL_SCREEN_TRIANGLE_GEOMETRY } from "../../../graphics/geometry/FULL_SCREEN_TRIANGLE_GEOMETRY.js";
5
- import { buildScreenSpaceFogOfWarShader } from "./screenSpaceFogOfWarShader.js";
6
-
7
- /**
8
- *
9
- * @param {FogOfWar} fow
10
- * @param {function(number, number, number, number)} callback
11
- */
12
- export function computeUvTransformFromFogOfWar(fow, callback) {
13
-
14
- const sampler = fow.sampler;
15
-
16
- const samplerWidth = sampler.width;
17
- const samplerHeight = sampler.height;
18
-
19
-
20
- const scaleX = (fow.scale * fow.size.x) * (samplerWidth / (samplerWidth - 3));
21
- const scaleY = (fow.scale * fow.size.y) * (samplerHeight / (samplerHeight - 3));
22
-
23
- const offsetX = 1.5 / samplerWidth;
24
- const offsetY = 1.5 / samplerHeight;
25
-
26
- callback(offsetX, offsetY, 1 / scaleX, 1 / scaleY);
27
- }
28
-
29
- export class FogOfWarRenderer {
30
- constructor() {
31
- /**
32
- *
33
- * @type {ShaderMaterial}
34
- */
35
- this.material = buildScreenSpaceFogOfWarShader();
36
-
37
- this.quad = new Mesh(FULL_SCREEN_TRIANGLE_GEOMETRY, this.material);
38
-
39
- this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
40
- this.scene = new Scene();
41
- this.scene.add(this.quad);
42
- }
43
-
44
- /**
45
- *
46
- * @param {FogOfWar} fow
47
- */
48
- setUvTransformFromFog(fow) {
49
- computeUvTransformFromFogOfWar(fow, (offsetX, offsetY, scaleX, scaleY) => {
50
-
51
- this.setUvTransform(offsetX, offsetY, scaleX, scaleY);
52
- });
53
- }
54
-
55
- /**
56
- *
57
- * @param {number} offsetX UV u offset, between 0 and 1
58
- * @param {number} offsetY UV v offset, between 0 and 1
59
- * @param {number} scaleX Transform scale from world coordinate position to UV u coordinate, minus offsetX
60
- * @param {number} scaleY Transform scale from world coordinate position to UV v coordinate, minus offsetY
61
- */
62
- setUvTransform(offsetX, offsetY, scaleX, scaleY) {
63
- assert.isNumber(offsetX, 'offsetX');
64
- assert.notNaN(offsetX, 'offsetX');
65
- assert.ok(isValueBetweenInclusive(offsetX, 0, 1), `expected offsetX to be between 0 and 1, instead was '${offsetX}'`);
66
-
67
- assert.isNumber(offsetY, 'offsetY');
68
- assert.notNaN(offsetY, 'offsetY');
69
- assert.ok(isValueBetweenInclusive(offsetY, 0, 1), `expected offsetY to be between 0 and 1, instead was '${offsetY}'`);
70
-
71
-
72
- assert.isNumber(scaleX, 'scaleX');
73
- assert.notNaN(scaleX, 'scaleX');
74
- assert.isFinite(scaleX, 'scaleX');
75
-
76
- assert.isNumber(scaleY, 'scaleY');
77
- assert.notNaN(scaleY, 'scaleY');
78
- assert.isFinite(scaleY, 'scaleY');
79
-
80
- this.material.uniforms.uFogUvTransform.value.set(offsetX, offsetY, scaleX, scaleY);
81
- }
82
-
83
- /**
84
- *
85
- * @param {Texture} texture
86
- */
87
- setDepthBuffer(texture) {
88
- // assert.equal(texture.type, UnsignedShortType, `expected texture type to be UnsignedShort(=${UnsignedShortType}), instead got something else (=${texture.type})`)
89
-
90
- this.material.uniforms.tDepth.value = texture;
91
- }
92
-
93
- /**
94
- *
95
- * @param {Texture} buffer
96
- */
97
- setFogBuffer(buffer) {
98
- this.material.uniforms.tFog.value = buffer;
99
-
100
- }
101
-
102
- /**
103
- * Resolution of the fog texture
104
- * @param {number} x
105
- * @param {number} y
106
- */
107
- setResolution(x, y) {
108
- this.material.uniforms.uResolution.value.set(x, y);
109
- }
110
-
111
- /**
112
- *
113
- * @param {Vector4} color
114
- */
115
- setFogColor(color) {
116
- this.material.uniforms.uColor.value.copy(color);
117
- }
118
-
119
- /**
120
- *
121
- * @param {WebGLRenderer} renderer
122
- * @param {PerspectiveCamera|OrthographicCamera} camera
123
- * @param {Scene} scene
124
- * @param {WebGLRenderTarget} target
125
- */
126
- render(renderer, camera, scene, target) {
127
- assert.ok(camera.isCamera, 'Not a camera');
128
- assert.ok(camera.isPerspectiveCamera || camera.isOrthographicCamera, 'Unsupported Camera type. Expected PerspectiveCamera or OrthographicCamera');
129
-
130
-
131
- //set up uniforms
132
- const uniforms = this.material.uniforms;
133
-
134
- uniforms.uProjectionInverse.value.copy(camera.projectionMatrixInverse);
135
- uniforms.uViewInverse.value.copy(camera.matrixWorld);
136
-
137
- renderer.setRenderTarget(target);
138
- renderer.clearColor();
139
-
140
- renderer.render(this.scene, this.camera);
141
-
142
-
143
- renderer.setRenderTarget(null);
144
- }
145
- }
1
+ import { Mesh, OrthographicCamera, Scene } from "three";
2
+ import { assert } from "../../../../core/assert.js";
3
+ import { isValueBetweenInclusive } from "../../../../core/math/interval/isValueBetweenInclusive.js";
4
+ import { FULL_SCREEN_TRIANGLE_GEOMETRY } from "../../../graphics/geometry/FULL_SCREEN_TRIANGLE_GEOMETRY.js";
5
+ import { FogOfWarEdgeMode } from "../FogOfWarEdgeMode.js";
6
+ import { buildScreenSpaceFogOfWarShader } from "./screenSpaceFogOfWarShader.js";
7
+
8
+ /**
9
+ *
10
+ * @param {FogOfWar} fow
11
+ * @param {function(number, number, number, number)} callback
12
+ */
13
+ export function computeUvTransformFromFogOfWar(fow, callback) {
14
+
15
+ const sampler = fow.sampler;
16
+
17
+ const samplerWidth = sampler.width;
18
+ const samplerHeight = sampler.height;
19
+
20
+
21
+ const scaleX = (fow.scale * fow.size.x) * (samplerWidth / (samplerWidth - 3));
22
+ const scaleY = (fow.scale * fow.size.y) * (samplerHeight / (samplerHeight - 3));
23
+
24
+ const offsetX = 1.5 / samplerWidth;
25
+ const offsetY = 1.5 / samplerHeight;
26
+
27
+ callback(offsetX, offsetY, 1 / scaleX, 1 / scaleY);
28
+ }
29
+
30
+ export class FogOfWarRenderer {
31
+ constructor() {
32
+ /**
33
+ *
34
+ * @type {ShaderMaterial}
35
+ */
36
+ this.material = buildScreenSpaceFogOfWarShader();
37
+
38
+ this.quad = new Mesh(FULL_SCREEN_TRIANGLE_GEOMETRY, this.material);
39
+
40
+ this.camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
41
+ this.scene = new Scene();
42
+ this.scene.add(this.quad);
43
+ }
44
+
45
+ /**
46
+ *
47
+ * @param {FogOfWar} fow
48
+ */
49
+ setUvTransformFromFog(fow) {
50
+ computeUvTransformFromFogOfWar(fow, (offsetX, offsetY, scaleX, scaleY) => {
51
+
52
+ this.setUvTransform(offsetX, offsetY, scaleX, scaleY);
53
+ });
54
+ }
55
+
56
+ /**
57
+ *
58
+ * @param {number} offsetX UV u offset, between 0 and 1
59
+ * @param {number} offsetY UV v offset, between 0 and 1
60
+ * @param {number} scaleX Transform scale from world coordinate position to UV u coordinate, minus offsetX
61
+ * @param {number} scaleY Transform scale from world coordinate position to UV v coordinate, minus offsetY
62
+ */
63
+ setUvTransform(offsetX, offsetY, scaleX, scaleY) {
64
+ assert.isNumber(offsetX, 'offsetX');
65
+ assert.notNaN(offsetX, 'offsetX');
66
+ assert.ok(isValueBetweenInclusive(offsetX, 0, 1), `expected offsetX to be between 0 and 1, instead was '${offsetX}'`);
67
+
68
+ assert.isNumber(offsetY, 'offsetY');
69
+ assert.notNaN(offsetY, 'offsetY');
70
+ assert.ok(isValueBetweenInclusive(offsetY, 0, 1), `expected offsetY to be between 0 and 1, instead was '${offsetY}'`);
71
+
72
+
73
+ assert.isNumber(scaleX, 'scaleX');
74
+ assert.notNaN(scaleX, 'scaleX');
75
+ assert.isFinite(scaleX, 'scaleX');
76
+
77
+ assert.isNumber(scaleY, 'scaleY');
78
+ assert.notNaN(scaleY, 'scaleY');
79
+ assert.isFinite(scaleY, 'scaleY');
80
+
81
+ this.material.uniforms.uFogUvTransform.value.set(offsetX, offsetY, scaleX, scaleY);
82
+ }
83
+
84
+ /**
85
+ *
86
+ * @param {Texture} texture
87
+ */
88
+ setDepthBuffer(texture) {
89
+ // assert.equal(texture.type, UnsignedShortType, `expected texture type to be UnsignedShort(=${UnsignedShortType}), instead got something else (=${texture.type})`)
90
+
91
+ this.material.uniforms.tDepth.value = texture;
92
+ }
93
+
94
+ /**
95
+ *
96
+ * @param {Texture} buffer
97
+ */
98
+ setFogBuffer(buffer) {
99
+ this.material.uniforms.tFog.value = buffer;
100
+
101
+ }
102
+
103
+ /**
104
+ * Resolution of the fog texture
105
+ * @param {number} x
106
+ * @param {number} y
107
+ */
108
+ setResolution(x, y) {
109
+ this.material.uniforms.uResolution.value.set(x, y);
110
+ }
111
+
112
+ /**
113
+ *
114
+ * @param {Vector4} color
115
+ */
116
+ setFogColor(color) {
117
+ this.material.uniforms.uColor.value.copy(color);
118
+ }
119
+
120
+ /**
121
+ * What the fog does outside the fogged area. See {@link FogOfWarEdgeMode}.
122
+ *
123
+ * @param {FogOfWarEdgeMode} mode
124
+ */
125
+ setEdgeMode(mode) {
126
+ this.material.uniforms.uEdgeExtend.value = (mode === FogOfWarEdgeMode.Extend) ? 1 : 0;
127
+ }
128
+
129
+ /**
130
+ *
131
+ * @param {WebGLRenderer} renderer
132
+ * @param {PerspectiveCamera|OrthographicCamera} camera
133
+ * @param {Scene} scene
134
+ * @param {WebGLRenderTarget} target
135
+ */
136
+ render(renderer, camera, scene, target) {
137
+ assert.ok(camera.isCamera, 'Not a camera');
138
+ assert.ok(camera.isPerspectiveCamera || camera.isOrthographicCamera, 'Unsupported Camera type. Expected PerspectiveCamera or OrthographicCamera');
139
+
140
+
141
+ //set up uniforms
142
+ const uniforms = this.material.uniforms;
143
+
144
+ uniforms.uProjectionInverse.value.copy(camera.projectionMatrixInverse);
145
+ uniforms.uViewInverse.value.copy(camera.matrixWorld);
146
+
147
+ renderer.setRenderTarget(target);
148
+ renderer.clearColor();
149
+
150
+ renderer.render(this.scene, this.camera);
151
+
152
+
153
+ renderer.setRenderTarget(null);
154
+ }
155
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"screenSpaceFogOfWarShader.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/fow/shader/screenSpaceFogOfWarShader.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH,kDAFW,cAAc,CAmKxB;+BAxKyE,OAAO"}
1
+ {"version":3,"file":"screenSpaceFogOfWarShader.d.ts","sourceRoot":"","sources":["../../../../../../src/engine/ecs/fow/shader/screenSpaceFogOfWarShader.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH,kDAFW,cAAc,CA4LxB;+BAjMyE,OAAO"}
@@ -22,9 +22,12 @@ export function buildScreenSpaceFogOfWarShader() {
22
22
  uniform vec2 uResolution;
23
23
 
24
24
  uniform vec4 uColor;
25
-
25
+
26
+ // FogOfWarEdgeMode as a float: 0 = Conceal (legacy), 1 = Extend
27
+ uniform float uEdgeExtend;
28
+
26
29
  varying vec2 vUv;
27
-
30
+
28
31
  const float c_samplesX = 5.0;
29
32
  const float c_samplesY = 5.0;
30
33
 
@@ -43,11 +46,9 @@ export function buildScreenSpaceFogOfWarShader() {
43
46
  // per tap, so a 5-tap kernel reaches +/-2 texels either side of centre.
44
47
  const float texelSpacing = 1.0;
45
48
 
46
- vec3 computeWorldPosition(){
49
+ vec3 computeWorldPosition(const in float normalizedDepth){
47
50
  // Convert screen coordinates to normalized device coordinates (NDC)
48
-
49
- float normalizedDepth = texture2D( tDepth, vUv).x ;
50
-
51
+
51
52
  vec4 ndc = vec4(
52
53
  (vUv.x - 0.5) * 2.0,
53
54
  (vUv.y - 0.5) * 2.0,
@@ -105,9 +106,28 @@ export function buildScreenSpaceFogOfWarShader() {
105
106
  }
106
107
 
107
108
  void main(){
109
+ float sceneDepth = texture2D( tDepth, vUv ).x;
110
+
111
+ // NOTHING WAS DRAWN HERE. The depth is the buffer's clear value, so
112
+ // the position reconstructed from it lands on the far plane: a point
113
+ // with no relationship to the scene, whose fog lookup is whatever the
114
+ // sampler's clamp happens to return. In a world with a visible
115
+ // boundary that is the concealed border ring, and it gets painted
116
+ // across the entire void as a hard rectangle on the world's edge.
117
+ // Under Extend, leave such a pixel alone rather than fog it on the
118
+ // strength of a reconstruction that means nothing.
119
+ //
120
+ // Under Conceal this branch never taken, so the legacy path is
121
+ // bit-identical — including the one case Extend gives up, a real
122
+ // surface sitting exactly on the far plane.
123
+ if (uEdgeExtend > 0.5 && sceneDepth >= 1.0) {
124
+ gl_FragColor = vec4(0.0);
125
+ return;
126
+ }
127
+
108
128
  //get world fragment position
109
- vec3 worldPosition = computeWorldPosition();
110
-
129
+ vec3 worldPosition = computeWorldPosition(sceneDepth);
130
+
111
131
  vec2 fogUv = worldPosition.xz * uFogUvTransform.zw + uFogUvTransform.xy;
112
132
 
113
133
  float fogValue = getFog( fogUv );
@@ -144,6 +164,11 @@ export function buildScreenSpaceFogOfWarShader() {
144
164
  uViewInverse: { type: 'm4', value: new Matrix4() },
145
165
  uProjectionInverse: { type: 'm4', value: new Matrix4() },
146
166
  uColor: { type: 'c', value: new Vector4(0, 0, 0, 1) },
167
+ /**
168
+ * {@link FogOfWarEdgeMode} as a float. Defaults to Conceal, so a consumer
169
+ * that never sets it keeps the pre-option behaviour.
170
+ */
171
+ uEdgeExtend: { type: 'f', value: 0 },
147
172
  uFogUvTransform: { type: 'v4', value: new Vector4(0, 0, 1, 1) },
148
173
  /**
149
174
  * Fog texture resolution