@woosh/meep-engine 2.166.0 → 2.168.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
@@ -1,95 +1,95 @@
1
- {
2
- "name": "@woosh/meep-engine",
3
- "license": "UNLICENSED",
4
- "productName": "Meep",
5
- "homepage": "https://meep.company-named.com/",
6
- "description": "Pure JavaScript game engine. Fully featured and production ready.",
7
- "type": "module",
8
- "author": "Alexander Goldring",
9
- "version": "2.166.0",
10
- "main": "build/meep.module.js",
11
- "module": "build/meep.module.js",
12
- "exports": {
13
- "./build/*": "./build/*",
14
- "./src/*": "./src/*",
15
- "./editor/*": "./editor/*",
16
- "./*.md": "./*"
17
- },
18
- "scripts": {
19
- "test:ci": "jest --config ./jest.conf.json --collectCoverage --coverageDirectory=\"./coverage\" --ci --reporters=default --reporters=jest-junit --watchAll=false",
20
- "build-module": "rollup -c rollup.config.js",
21
- "publish-npm": "npm publish",
22
- "generate-types": "npx -p typescript tsc --project tsconfig.types.json || exit 0;"
23
- },
24
- "browserslist": [
25
- "last 2 chrome version",
26
- "last 2 firefox version",
27
- "last 2 safari version"
28
- ],
29
- "files": [
30
- "build",
31
- "src",
32
- "editor",
33
- "samples",
34
- "package.json",
35
- "README.md",
36
- "!src/**/*.spec.js",
37
- "!editor/**/*.spec.js"
38
- ],
39
- "dependencies": {
40
- "gl-matrix": "3.4.3",
41
- "opentype.js": "1.3.3",
42
- "pako": "2.0.3",
43
- "robust-predicates": "3.0.2"
44
- },
45
- "peerDependencies": {
46
- "three": ">=0.135.0"
47
- },
48
- "peerDependenciesMeta": {
49
- "three": {
50
- "optional": true
51
- }
52
- },
53
- "devDependencies": {
54
- "@babel/core": "7.22.15",
55
- "@babel/preset-env": "7.22.15",
56
- "@rollup/plugin-commonjs": "25.0.2",
57
- "@rollup/plugin-node-resolve": "15.1.0",
58
- "@rollup/plugin-strip": "3.0.2",
59
- "@rollup/plugin-terser": "0.4.3",
60
- "@types/jest": "29.5.12",
61
- "@types/three": "^0.135.0",
62
- "babel-jest": "29.7.0",
63
- "jest": "29.7.0",
64
- "jest-environment-jsdom": "29.7.0",
65
- "jest-junit": "16.0.0",
66
- "rollup": "3.26.2",
67
- "typescript": "5.2.2"
68
- },
69
- "keywords": [
70
- "game-engine",
71
- "gamedev",
72
- "ecs",
73
- "entity-component-system",
74
- "data-oriented",
75
- "zero-allocation",
76
- "modular",
77
- "tree-shaking",
78
- "3d",
79
- "webgl",
80
- "webgl2",
81
- "webgpu",
82
- "webaudio",
83
- "particles",
84
- "bvh",
85
- "behavior-tree",
86
- "inverse-kinematics",
87
- "fabrik",
88
- "clustered-lighting",
89
- "color-management",
90
- "typescript"
91
- ],
92
- "engines": {
93
- "node": ">=24"
94
- }
95
- }
1
+ {
2
+ "name": "@woosh/meep-engine",
3
+ "license": "UNLICENSED",
4
+ "productName": "Meep",
5
+ "homepage": "https://meep.company-named.com/",
6
+ "description": "Pure JavaScript game engine. Fully featured and production ready.",
7
+ "type": "module",
8
+ "author": "Alexander Goldring",
9
+ "version": "2.168.0",
10
+ "main": "build/meep.module.js",
11
+ "module": "build/meep.module.js",
12
+ "exports": {
13
+ "./build/*": "./build/*",
14
+ "./src/*": "./src/*",
15
+ "./editor/*": "./editor/*",
16
+ "./*.md": "./*"
17
+ },
18
+ "scripts": {
19
+ "test:ci": "jest --config ./jest.conf.json --collectCoverage --coverageDirectory=\"./coverage\" --ci --reporters=default --reporters=jest-junit --watchAll=false",
20
+ "build-module": "rollup -c rollup.config.js",
21
+ "publish-npm": "npm publish",
22
+ "generate-types": "npx -p typescript tsc --project tsconfig.types.json || exit 0;"
23
+ },
24
+ "browserslist": [
25
+ "last 2 chrome version",
26
+ "last 2 firefox version",
27
+ "last 2 safari version"
28
+ ],
29
+ "files": [
30
+ "build",
31
+ "src",
32
+ "editor",
33
+ "samples",
34
+ "package.json",
35
+ "README.md",
36
+ "!src/**/*.spec.js",
37
+ "!editor/**/*.spec.js"
38
+ ],
39
+ "dependencies": {
40
+ "gl-matrix": "3.4.3",
41
+ "opentype.js": "1.3.3",
42
+ "pako": "2.0.3",
43
+ "robust-predicates": "3.0.2"
44
+ },
45
+ "peerDependencies": {
46
+ "three": ">=0.135.0"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "three": {
50
+ "optional": true
51
+ }
52
+ },
53
+ "devDependencies": {
54
+ "@babel/core": "7.22.15",
55
+ "@babel/preset-env": "7.22.15",
56
+ "@rollup/plugin-commonjs": "25.0.2",
57
+ "@rollup/plugin-node-resolve": "15.1.0",
58
+ "@rollup/plugin-strip": "3.0.2",
59
+ "@rollup/plugin-terser": "0.4.3",
60
+ "@types/jest": "29.5.12",
61
+ "@types/three": "^0.135.0",
62
+ "babel-jest": "29.7.0",
63
+ "jest": "29.7.0",
64
+ "jest-environment-jsdom": "29.7.0",
65
+ "jest-junit": "16.0.0",
66
+ "rollup": "3.26.2",
67
+ "typescript": "5.2.2"
68
+ },
69
+ "keywords": [
70
+ "game-engine",
71
+ "gamedev",
72
+ "ecs",
73
+ "entity-component-system",
74
+ "data-oriented",
75
+ "zero-allocation",
76
+ "modular",
77
+ "tree-shaking",
78
+ "3d",
79
+ "webgl",
80
+ "webgl2",
81
+ "webgpu",
82
+ "webaudio",
83
+ "particles",
84
+ "bvh",
85
+ "behavior-tree",
86
+ "inverse-kinematics",
87
+ "fabrik",
88
+ "clustered-lighting",
89
+ "color-management",
90
+ "typescript"
91
+ ],
92
+ "engines": {
93
+ "node": ">=24"
94
+ }
95
+ }
@@ -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,40 @@
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 a pixel with no geometry is located by projecting its camera
28
+ * ray onto the fog's own ground plane rather than read off the far plane.
29
+ *
30
+ * For a world with a VISIBLE boundary — a finite arena the camera can see
31
+ * past. Under {@link FogOfWarEdgeMode.Conceal} such a scene gets a hard black
32
+ * rectangle drawn exactly on its boundary (the concealed border ring, clamped
33
+ * across the whole void), which both advertises where the map stops and paints
34
+ * over anything drawn earlier to hide it — this layer composites last, over
35
+ * every other effect. Under Extend the fog agrees with the ground it borders:
36
+ * explored terrain stays clear right out past the boundary, unexplored terrain
37
+ * stays dark, and nothing marks where the geometry ends.
38
+ */
39
+ Extend: 1,
40
+ };
@@ -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,CAmNxB;+BAxNyE,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,51 @@ export function buildScreenSpaceFogOfWarShader() {
105
106
  }
106
107
 
107
108
  void main(){
108
- //get world fragment position
109
- vec3 worldPosition = computeWorldPosition();
110
-
109
+ float sceneDepth = texture2D( tDepth, vUv ).x;
110
+
111
+ vec3 worldPosition;
112
+
113
+ if (uEdgeExtend > 0.5 && sceneDepth >= 1.0) {
114
+ // NOTHING WAS DRAWN HERE, so there is no surface to locate: the
115
+ // depth is the buffer's clear value and computeWorldPosition would
116
+ // hand back a point on the far plane, unrelated to the scene, whose
117
+ // fog lookup is whatever the sampler's clamp happens to return.
118
+ //
119
+ // The fog is a GROUND field though — its sampler is indexed by
120
+ // world XZ and anchored at the world origin (uFogUvTransform is
121
+ // built from size and scale alone, with no Y term), so the
122
+ // meaningful position for such a pixel is where its camera ray
123
+ // crosses that field's own plane, y = 0.
124
+ //
125
+ // PROJECTING rather than skipping is what keeps a world boundary
126
+ // invisible. At the last pixel of ground the surface hit and the
127
+ // plane hit are the same point, so the fog value carries straight
128
+ // on across the edge; skipping instead steps it from whatever the
129
+ // edge reads to nothing at all, and that step is a visible line
130
+ // exactly where the world stops.
131
+ vec3 camPos = uViewInverse[3].xyz;
132
+ vec3 dir = computeWorldPosition(1.0) - camPos;
133
+
134
+ // parallel to the plane, or pointing away from it: nothing to
135
+ // sample, so leave the pixel alone
136
+ if (abs(dir.y) < 1e-6) {
137
+ gl_FragColor = vec4(0.0);
138
+ return;
139
+ }
140
+
141
+ float t = -camPos.y / dir.y;
142
+
143
+ if (t <= 0.0) {
144
+ gl_FragColor = vec4(0.0);
145
+ return;
146
+ }
147
+
148
+ worldPosition = camPos + dir * t;
149
+ } else {
150
+ //get world fragment position
151
+ worldPosition = computeWorldPosition(sceneDepth);
152
+ }
153
+
111
154
  vec2 fogUv = worldPosition.xz * uFogUvTransform.zw + uFogUvTransform.xy;
112
155
 
113
156
  float fogValue = getFog( fogUv );
@@ -144,6 +187,11 @@ export function buildScreenSpaceFogOfWarShader() {
144
187
  uViewInverse: { type: 'm4', value: new Matrix4() },
145
188
  uProjectionInverse: { type: 'm4', value: new Matrix4() },
146
189
  uColor: { type: 'c', value: new Vector4(0, 0, 0, 1) },
190
+ /**
191
+ * {@link FogOfWarEdgeMode} as a float. Defaults to Conceal, so a consumer
192
+ * that never sets it keeps the pre-option behaviour.
193
+ */
194
+ uEdgeExtend: { type: 'f', value: 0 },
147
195
  uFogUvTransform: { type: 'v4', value: new Vector4(0, 0, 1, 1) },
148
196
  /**
149
197
  * Fog texture resolution