@woosh/meep-engine 2.33.65 → 2.33.67

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.
@@ -315,8 +315,9 @@ export class QuadTreeNode extends AABB2 {
315
315
  const x1 = this.x1;
316
316
  const y1 = this.y1;
317
317
 
318
- const xm = (x0 + x1) / 2;
319
- const ym = (y0 + y1) / 2;
318
+ // get center point
319
+ const xm = (x0 + x1) * 0.5;
320
+ const ym = (y0 + y1) * 0.5;
320
321
 
321
322
  for (; i < dataLength; i++) {
322
323
  const datum = data[i];
@@ -1,4 +1,5 @@
1
1
  import { Matrix4, NormalBlending, ShaderMaterial, Vector2, Vector4 } from "three";
2
+ import { FULL_SCREEN_QUAD_VERTEX_SHADER } from "../../../graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js";
2
3
 
3
4
  /**
4
5
  Shader for drawing fog of war as a screen-space post-process effect
@@ -6,16 +7,7 @@ import { Matrix4, NormalBlending, ShaderMaterial, Vector2, Vector4 } from "three
6
7
  */
7
8
  export function buildScreenSpaceFogOfWarShader() {
8
9
  function vertexShader() {
9
- return `
10
- varying vec2 vUv;
11
-
12
- void main() {
13
-
14
- vUv = uv;
15
-
16
- gl_Position = vec4( (uv - 0.5)*2.0, 0.0, 1.0 );
17
-
18
- }`;
10
+ return FULL_SCREEN_QUAD_VERTEX_SHADER;
19
11
  }
20
12
 
21
13
  function fragmentShader() {
@@ -5,11 +5,10 @@
5
5
 
6
6
  import { ClampToEdgeWrapping, DataTexture, RGBAFormat, UnsignedByteType, Vector2 } from 'three';
7
7
  import { Sampler2D } from '../../graphics/texture/sampler/Sampler2D.js';
8
- import sampler2D2Texture from '../../graphics/texture/sampler/Sampler2D2Texture.js';
9
8
  import AmbientOcclusionShader from '../../graphics/shaders/AmbientOcclusionShader.js';
10
- import GaussianBlurShader from '../../graphics/shaders/GaussianBlurShader.js';
11
9
  import ImageFilter from '../../graphics/filter/ImageFilter.js';
12
10
  import { sampler2DtoFloat32Texture } from "../../graphics/texture/sampler/sampler2DToFloat32Texture.js";
11
+ import { DenoiseShader } from "../../graphics/shaders/DenoiseShader.js";
13
12
 
14
13
  function filterResult2Texture(data, width, height) {
15
14
  const result = new DataTexture();
@@ -37,6 +36,7 @@ function filterResult2Texture(data, width, height) {
37
36
  * @param {Sampler2D} heightMap
38
37
  * @param {Sampler2D} normalMap
39
38
  * @param {Vector2} resultSize
39
+ * @param worldSize
40
40
  * @param {number} [rayLength]
41
41
  * @returns {Sampler2D}
42
42
  */
@@ -45,13 +45,13 @@ function normalMap2OcclusionMap(
45
45
  heightMap,
46
46
  normalMap,
47
47
  resultSize,
48
+ worldSize = new Vector2(heightMap.width, heightMap.width),
48
49
  rayLength = 17
49
50
  ) {
50
51
  const width = resultSize.x;
51
52
  const height = resultSize.y;
52
- const resolution = new Vector2(heightMap.width, heightMap.height);
53
53
  //
54
- const normalTexture = sampler2D2Texture(normalMap, 255, 0.5);
54
+ const normalTexture = sampler2DtoFloat32Texture(normalMap);
55
55
 
56
56
  const heightTexture = sampler2DtoFloat32Texture(heightMap);
57
57
 
@@ -60,17 +60,16 @@ function normalMap2OcclusionMap(
60
60
  const shaderAO = new AmbientOcclusionShader();
61
61
  shaderAO.uniforms.heightMap.value = heightTexture;
62
62
  shaderAO.uniforms.normalMap.value = normalTexture;
63
- shaderAO.uniforms.resolution.value = resolution;
63
+ shaderAO.uniforms.world_size.value.set(worldSize.x, worldSize.y);
64
64
  shaderAO.uniforms.rayLength.value = rayLength;
65
65
  //perform filtering
66
66
  const rawAO = ImageFilter(renderer, width, height, shaderAO);
67
67
 
68
- const shaderBlur = new GaussianBlurShader();
69
- shaderBlur.uniforms.resolution.value = resolution;
70
- shaderBlur.uniforms.tDiffuse.value = filterResult2Texture(rawAO.array, width, height);
71
- shaderBlur.uniforms.sigma.value.set(1.3, 1.3);
68
+ const shaderDenoise = new DenoiseShader(2);
69
+ shaderDenoise.uniforms.source.value = filterResult2Texture(rawAO.array, width, height);
70
+ shaderDenoise.uniforms.pixelOffset.value.set(1 / width, 1 / height);
72
71
 
73
- const smoothAO = ImageFilter(renderer, width, height, shaderBlur);
72
+ const smoothAO = ImageFilter(renderer, width, height, shaderDenoise);
74
73
 
75
74
  //create the sampler
76
75
  const result = new Sampler2D(new Uint8ClampedArray(width * height), 1, width, height);
@@ -79,9 +78,7 @@ function normalMap2OcclusionMap(
79
78
  const size = width * height;
80
79
 
81
80
  for (let i = 0; i < size; i++) {
82
- const i4 = i * 4;
83
-
84
- result.data[i] = smoothAO.array[i4];
81
+ result.data[i] = smoothAO.array[i * 4];
85
82
  }
86
83
 
87
84
  return result;
@@ -0,0 +1,106 @@
1
+ import normalMap2AOMap from "../../grid/NormalMap2AOMap.js";
2
+ import heightMap2NormalMap from "../../grid/HeightMap2NormalMap.js";
3
+ import { WebGLRendererPool } from "../../../graphics/render/RendererPool.js";
4
+ import Vector2 from "../../../../core/geom/Vector2.js";
5
+ import { writeSample2DDataToDataTexture } from "../../../graphics/texture/sampler/writeSampler2DDataToDataTexture.js";
6
+
7
+ /**
8
+ *
9
+ * @param {WebGLRenderer} renderer
10
+ * @param {Sampler2D} sampler
11
+ * @returns {Promise<Sampler2D>}
12
+ */
13
+ function promiseSamplerNormal(renderer, sampler) {
14
+
15
+ console.time("generating normal map");
16
+ const normalSampler = heightMap2NormalMap(renderer, sampler);
17
+ console.timeEnd("generating normal map");
18
+
19
+
20
+ return Promise.resolve(normalSampler);
21
+ }
22
+
23
+ /**
24
+ *
25
+ * @param {WebGLRenderer} renderer
26
+ * @param {Promise<Sampler2D>} pSamplerNormal
27
+ * @param {Sampler2D} samplerHeight
28
+ * @param {Vector2} resolution
29
+ * @param {Vector2} [wordSize]
30
+ * @param {number} rayLength
31
+ * @returns {Promise<unknown>}
32
+ */
33
+ function promiseSamplerAO(
34
+ {
35
+ renderer,
36
+ pSamplerNormal,
37
+ samplerHeight,
38
+ resolution,
39
+ wordSize = resolution,
40
+ rayLength
41
+ }
42
+ ) {
43
+ return new Promise(function (resolve, reject) {
44
+ Promise.all([pSamplerNormal]).then(function (values) {
45
+ const samplerNormal = values[0];
46
+ console.time("generating AO map");
47
+ const occlusionSampler = normalMap2AOMap(
48
+ renderer,
49
+ samplerHeight,
50
+ samplerNormal,
51
+ wordSize,
52
+ resolution,
53
+ rayLength
54
+ );
55
+ console.timeEnd("generating AO map");
56
+ //console.info(occlusionSampler);
57
+ //
58
+ // paintSamplerOnHTML(samplerHeight, 0, 0, 40, 3);
59
+ // paintSamplerOnHTML(samplerNormal, 1, 0, 128, 1);
60
+ // paintSamplerOnHTML(occlusionSampler, 2, 0, 1, 0);
61
+ //
62
+ //
63
+ resolve(occlusionSampler);
64
+ }, reject);
65
+ });
66
+ }
67
+
68
+ /**
69
+ *
70
+ * @param {THREE.DataTexture} texture
71
+ * @param {Vector2} [resolution]
72
+ * @param {Sampler2D} heightSampler
73
+ * @param {number} [rayLength]
74
+ * @param {Vector2} [worldSize]
75
+ * @returns {Promise}
76
+ */
77
+ export function buildLightTexture({
78
+ texture,
79
+ heightSampler,
80
+ resolution= new Vector2(texture.image.width, texture.image.height),
81
+ rayLength = 11,
82
+ worldSize = resolution
83
+ }) {
84
+
85
+ const renderer = WebGLRendererPool.global.get();
86
+
87
+ const normal = promiseSamplerNormal(renderer, heightSampler);
88
+ const ao = promiseSamplerAO({
89
+ renderer: renderer,
90
+ pSamplerNormal: normal,
91
+ samplerHeight: heightSampler,
92
+ resolution: resolution,
93
+ rayLength: rayLength,
94
+ worldSize
95
+ });
96
+
97
+ const promise = ao.then(sampler => {
98
+ writeSample2DDataToDataTexture(sampler, texture);
99
+ });
100
+
101
+ Promise.all([normal, ao]).finally(() => {
102
+ WebGLRendererPool.global.release(renderer);
103
+ });
104
+
105
+ return promise;
106
+ }
@@ -0,0 +1,59 @@
1
+ import { BinaryBuffer } from "../../../../core/binary/BinaryBuffer.js";
2
+ import { deserializeTexture } from "../../../graphics/texture/sampler/TextureBinaryBufferSerializer.js";
3
+ import { Sampler2D } from "../../../graphics/texture/sampler/Sampler2D.js";
4
+ import { GameAssetType } from "../../../asset/GameAssetType.js";
5
+ import rgbaData2valueSampler2D from "../../../graphics/texture/sampler/rgbaData2valueSampler2D.js";
6
+
7
+ /**
8
+ *
9
+ * @param zRange
10
+ * @param heightMapURL
11
+ * @param {AssetManager} assetManager
12
+ * @returns {Promise<any>}
13
+ */
14
+ export function promiseSamplerHeight(zRange, heightMapURL, assetManager) {
15
+ return new Promise(function (fulfill, reject) {
16
+
17
+ function assetObtained(asset) {
18
+ const array = asset.create();
19
+
20
+ const binaryBuffer = new BinaryBuffer();
21
+ //
22
+ // binaryBuffer.writeUint32(2560);
23
+ // binaryBuffer.writeUint32(2560);
24
+ //
25
+ // binaryBuffer.writeUint8(1);
26
+ //
27
+ // binaryBuffer.writeUint8(6);
28
+
29
+ binaryBuffer.writeBytes(new Uint8Array(array), 0, array.length);
30
+
31
+ binaryBuffer.position = 0;
32
+
33
+ const sampler2D = deserializeTexture(binaryBuffer);
34
+ fulfill(sampler2D);
35
+ }
36
+
37
+
38
+ if (heightMapURL === undefined) {
39
+ console.warn('Height map is not specified');
40
+ const defaultSampler = new Sampler2D(new Uint8Array(1), 1, 1, 1);
41
+ fulfill(defaultSampler);
42
+ } else if (heightMapURL.endsWith('.bin')) {
43
+ //load texture from a binary file
44
+ assetManager.get(heightMapURL, GameAssetType.ArrayBuffer, assetObtained, reject);
45
+ } else {
46
+ assetManager.get(heightMapURL, GameAssetType.Image, function (asset) {
47
+ const image = asset.create();
48
+
49
+ // plane
50
+ const imgWidth = image.width;
51
+ const imgHeight = image.height;
52
+
53
+ const samplerHeight = rgbaData2valueSampler2D(image.data, imgWidth, imgHeight, zRange, -zRange / 2);
54
+
55
+ fulfill(samplerHeight);
56
+ }, reject);
57
+ }
58
+ });
59
+ }
@@ -14,9 +14,6 @@ import {
14
14
  } from 'three';
15
15
  import { clamp } from "../../../../core/math/clamp.js";
16
16
  import { Sampler2D } from '../../../graphics/texture/sampler/Sampler2D.js';
17
- import heightMap2NormalMap from '../../grid/HeightMap2NormalMap.js';
18
- import normalMap2AOMap from '../../grid/NormalMap2AOMap.js';
19
- import rgbaData2valueSampler2D from '../../../graphics/texture/sampler/rgbaData2valueSampler2D.js';
20
17
  import { BinaryNode } from '../../../../core/bvh2/BinaryNode.js';
21
18
  import Vector2 from '../../../../core/geom/Vector2.js';
22
19
  import { TerrainOverlay } from '../overlay/TerrainOverlay.js';
@@ -24,13 +21,9 @@ import { TerrainOverlay } from '../overlay/TerrainOverlay.js';
24
21
  import Clouds from '../TerrainClouds.js';
25
22
 
26
23
  import TerrainTileManager from '../tiles/TerrainTileManager.js';
27
- import { WebGLRendererPool } from "../../../graphics/render/RendererPool.js";
28
- import { deserializeTexture } from "../../../graphics/texture/sampler/TextureBinaryBufferSerializer.js";
29
- import { BinaryBuffer } from "../../../../core/binary/BinaryBuffer.js";
30
24
  import { TerrainPreview } from "../TerrainPreview.js";
31
25
  import { assert } from "../../../../core/assert.js";
32
26
  import { GameAssetType } from "../../../asset/GameAssetType.js";
33
- import { writeSample2DDataToDataTexture } from "../../../graphics/texture/sampler/writeSampler2DDataToDataTexture.js";
34
27
  import { TerrainLayers } from "./layers/TerrainLayers.js";
35
28
  import { SplatMaterial } from "../../../graphics/material/SplatMaterial.js";
36
29
  import { loadLegacyTerrainSplats, SplatMapping } from "./splat/SplatMapping.js";
@@ -41,147 +34,8 @@ import { MeepSettings } from "../../../MeepSettings.js";
41
34
  import { loadLegacyTerrainLayers } from "./layers/loadLegacyTerrainLayers.js";
42
35
  import { TerrainFlags } from "./TerrainFlags.js";
43
36
  import { IllegalStateException } from "../../../../core/fsm/exceptions/IllegalStateException.js";
44
-
45
- /**
46
- *
47
- * @param zRange
48
- * @param heightMapURL
49
- * @param {AssetManager} assetManager
50
- * @returns {Promise<any>}
51
- */
52
- function promiseSamplerHeight(zRange, heightMapURL, assetManager) {
53
- return new Promise(function (fulfill, reject) {
54
-
55
- function assetObtained(asset) {
56
- const array = asset.create();
57
-
58
- const binaryBuffer = new BinaryBuffer();
59
- //
60
- // binaryBuffer.writeUint32(2560);
61
- // binaryBuffer.writeUint32(2560);
62
- //
63
- // binaryBuffer.writeUint8(1);
64
- //
65
- // binaryBuffer.writeUint8(6);
66
-
67
- binaryBuffer.writeBytes(new Uint8Array(array), 0, array.length);
68
-
69
- binaryBuffer.position = 0;
70
-
71
- const sampler2D = deserializeTexture(binaryBuffer);
72
- fulfill(sampler2D);
73
- }
74
-
75
-
76
- if (heightMapURL === undefined) {
77
- console.warn('Height map is not specified');
78
- const defaultSampler = new Sampler2D(new Uint8Array(1), 1, 1, 1);
79
- fulfill(defaultSampler);
80
- } else if (heightMapURL.endsWith('.bin')) {
81
- //load texture from a binary file
82
- assetManager.get(heightMapURL, GameAssetType.ArrayBuffer, assetObtained, reject);
83
- } else {
84
- assetManager.get(heightMapURL, GameAssetType.Image, function (asset) {
85
- const image = asset.create();
86
-
87
- // plane
88
- const imgWidth = image.width;
89
- const imgHeight = image.height;
90
-
91
- const samplerHeight = rgbaData2valueSampler2D(image.data, imgWidth, imgHeight, zRange, -zRange / 2);
92
-
93
- fulfill(samplerHeight);
94
- }, reject);
95
- }
96
- });
97
- }
98
-
99
- /**
100
- *
101
- * @param {WebGLRenderer} renderer
102
- * @param {Sampler2D} sampler
103
- * @returns {Promise<Sampler2D>}
104
- */
105
- function promiseSamplerNormal(renderer, sampler) {
106
-
107
- console.time("generating normal map");
108
- const normalSampler = heightMap2NormalMap(renderer, sampler);
109
- console.timeEnd("generating normal map");
110
-
111
-
112
- return Promise.resolve(normalSampler);
113
- }
114
-
115
- /**
116
- *
117
- * @param {WebGLRenderer} renderer
118
- * @param {Promise<Sampler2D>} pSamplerNormal
119
- * @param {Sampler2D} samplerHeight
120
- * @param {Vector2} resolution
121
- * @param {number} rayLength
122
- * @returns {Promise<unknown>}
123
- */
124
- function promiseSamplerAO(
125
- renderer,
126
- pSamplerNormal,
127
- samplerHeight,
128
- resolution,
129
- rayLength
130
- ) {
131
- return new Promise(function (resolve, reject) {
132
- Promise.all([pSamplerNormal]).then(function (values) {
133
- const samplerNormal = values[0];
134
- console.time("generating AO map");
135
- const occlusionSampler = normalMap2AOMap(
136
- renderer,
137
- samplerHeight,
138
- samplerNormal,
139
- resolution,
140
- rayLength
141
- );
142
- console.timeEnd("generating AO map");
143
- //console.info(occlusionSampler);
144
- //
145
- // paintSamplerOnHTML(samplerHeight, 0, 0, 40, 3);
146
- // paintSamplerOnHTML(samplerNormal, 1, 0, 128, 1);
147
- // paintSamplerOnHTML(occlusionSampler, 2, 0, 1, 0);
148
- //
149
- //
150
- resolve(occlusionSampler);
151
- }, reject);
152
- });
153
- }
154
-
155
- /**
156
- *
157
- * @param {DataTexture} texture
158
- * @param {Vector2} resolution
159
- * @param {Sampler2D} heightSampler
160
- * @param {number} heightRange
161
- * @param {number} [rayLength]
162
- * @returns {Promise}
163
- */
164
- function buildLightTexture({ texture, heightSampler, resolution, rayLength = 17 }) {
165
-
166
- const renderer = WebGLRendererPool.global.get();
167
-
168
- if (resolution === undefined) {
169
- resolution = new Vector2(texture.image.width, texture.image.height);
170
- }
171
-
172
- const normal = promiseSamplerNormal(renderer, heightSampler);
173
- const ao = promiseSamplerAO(renderer, normal, heightSampler, resolution, rayLength);
174
-
175
- const promise = ao.then(sampler => {
176
- writeSample2DDataToDataTexture(sampler, texture);
177
- });
178
-
179
- Promise.all([normal, ao]).finally(() => {
180
- WebGLRendererPool.global.release(renderer);
181
- });
182
-
183
- return promise;
184
- }
37
+ import { buildLightTexture } from "./BuildLightTexture.js";
38
+ import { promiseSamplerHeight } from "./PromiseSamplerHeight.js";
185
39
 
186
40
  let idCounter = 0;
187
41
 
@@ -903,13 +757,13 @@ class Terrain {
903
757
  this.updateWorkerHeights();
904
758
  }
905
759
 
906
- updateWorkerHeights() {
760
+ async updateWorkerHeights() {
907
761
  const s = this.samplerHeight;
908
762
  const worker = this.buildWorker;
909
763
 
910
- worker.setHeightSampler(s.data, s.itemSize, s.width, s.height);
764
+ await worker.setHeightSampler(s.data, s.itemSize, s.width, s.height);
911
765
 
912
- return worker.computeHeightRange()
766
+ return await worker.computeHeightRange()
913
767
  .then(({ min, max }) => {
914
768
  this.tiles.setHeightRange(min, max);
915
769
  });
@@ -1012,7 +866,12 @@ class Terrain {
1012
866
  this.setFlag(TerrainFlags.Built);
1013
867
  }
1014
868
 
1015
- buildLightMap() {
869
+ /**
870
+ *
871
+ * @param {number} quality resolution in texels per grid cell (4 = 4x4 light map texels per single grid cell)
872
+ * @return {Promise<DataTexture>}
873
+ */
874
+ buildLightMap(quality = 4) {
1016
875
 
1017
876
 
1018
877
  const size_x = this.size.x;
@@ -1035,7 +894,9 @@ class Terrain {
1035
894
 
1036
895
  const promise = buildLightTexture({
1037
896
  texture,
1038
- heightSampler: this.samplerHeight
897
+ heightSampler: this.samplerHeight,
898
+ worldSize: new Vector2(size_x * this.gridScale, size_y * this.gridScale),
899
+ resolution: new Vector2(size_x * quality, size_y *quality)
1039
900
  });
1040
901
 
1041
902
 
@@ -0,0 +1,10 @@
1
+ export const FULL_SCREEN_QUAD_VERTEX_SHADER = `
2
+ varying vec2 vUv;
3
+
4
+ void main() {
5
+
6
+ vUv = uv;
7
+
8
+ gl_Position = vec4( (uv - 0.5)*2.0, 0.0, 1.0 );
9
+
10
+ }`;
@@ -5,13 +5,14 @@
5
5
 
6
6
  import { LinearFilter, Mesh, OrthographicCamera, Scene, ShaderMaterial, WebGLRenderTarget } from 'three';
7
7
  import { FULL_SCREEN_TRIANGLE_GEOMETRY } from "../geometry/FULL_SCREEN_TRIANGLE_GEOMETRY.js";
8
+ import { FULL_SCREEN_QUAD_VERTEX_SHADER } from "../FULL_SCREEN_QUAD_VERTEX_SHADER.js";
8
9
 
9
10
  /**
10
11
  *
11
12
  * @param {WebGLRenderer} renderer
12
13
  * @param {number} width
13
14
  * @param {number} height
14
- * @param {{vertexShader:string,fragmentShader:string, uniforms?:Object, defines?:Object}} processShader
15
+ * @param {{vertexShader?:string,fragmentShader:string, uniforms?:Object, defines?:Object}} processShader
15
16
  * @param {Uint8Array} [destination]
16
17
  * @return {{array: Uint8Array, renderer: WebGLRenderer}}
17
18
  */
@@ -34,9 +35,12 @@ function processTexture(
34
35
  scene.add(camera);
35
36
 
36
37
  //make a quad to render
37
- const material = new ShaderMaterial(processShader);
38
+ const material = new ShaderMaterial(Object.assign({ vertexShader: FULL_SCREEN_QUAD_VERTEX_SHADER }, processShader));
39
+
38
40
  material.lights = false;
39
41
  material.fog = false;
42
+ material.uniformsNeedUpdate = true;
43
+ material.needsUpdate = true;
40
44
 
41
45
  const quad = new Mesh(FULL_SCREEN_TRIANGLE_GEOMETRY, material);
42
46
 
@@ -43,10 +43,15 @@ export async function convert_three_object_to_micron(object, micron_cache = defa
43
43
  return attached_micron;
44
44
  }
45
45
 
46
- const existing = await micron_cache.get(g);
46
+ const cached_micron = await micron_cache.get(g);
47
47
 
48
- if (existing !== undefined && existing !== null) {
49
- return existing;
48
+
49
+ if (cached_micron !== undefined && cached_micron !== null) {
50
+
51
+ // set proxy
52
+ cached_micron.proxy = g;
53
+
54
+ return cached_micron;
50
55
  }
51
56
 
52
57
  const pending = pending_cache.get(g);
@@ -61,8 +66,14 @@ export async function convert_three_object_to_micron(object, micron_cache = defa
61
66
 
62
67
  promises.push(micron_geometry_promise);
63
68
 
69
+ /**
70
+ *
71
+ * @type {MicronGeometry}
72
+ */
64
73
  const micron_geometry = await micron_geometry_promise;
65
74
 
75
+ micron_geometry.proxy = g;
76
+
66
77
  await micron_cache.set(g, micron_geometry);
67
78
 
68
79
  return micron_geometry;
@@ -106,6 +106,9 @@ class GLTFAssetTransformer extends AssetTransformer {
106
106
 
107
107
  this.__serde.deserialize(buffer, micron_geometry);
108
108
 
109
+ // set proxy for raycasting
110
+ micron_geometry.proxy = geometry;
111
+
109
112
  geometry[MICRON_GEOMETRY_FIELD] = micron_geometry;
110
113
  }
111
114
 
@@ -3,9 +3,6 @@
3
3
  */
4
4
  import { Vector2 } from "three";
5
5
 
6
-
7
- const samplesPerPixel = 37;
8
-
9
6
  const AmbientOcclusionShader = function () {
10
7
  return {
11
8
 
@@ -13,11 +10,16 @@ const AmbientOcclusionShader = function () {
13
10
 
14
11
  "normalMap": { type: "t", value: null },
15
12
  "heightMap": { type: "t", value: null },
16
- "resolution": { type: "v2", value: new Vector2(512, 512) },
13
+ "world_size": { type: "v2", value: new Vector2(512, 512) },
17
14
  "rayLength": { type: 'f', value: 17 }
18
15
 
19
16
  },
20
17
 
18
+ defines: {
19
+ 'NUM_SAMPLES': 64,
20
+ 'NUM_RINGS': 7,
21
+ },
22
+
21
23
  vertexShader: [
22
24
 
23
25
  "varying vec2 vUv;",
@@ -33,17 +35,17 @@ const AmbientOcclusionShader = function () {
33
35
 
34
36
  fragmentShader: [
35
37
  `
36
- uniform vec2 resolution;
37
38
  uniform sampler2D normalMap;
38
39
  uniform sampler2D heightMap;
39
40
  uniform float rayLength;
41
+ uniform vec2 world_size;
40
42
 
41
43
  varying vec2 vUv;
42
44
 
43
45
  vec3 get(float x, float y){
44
- vec2 _uv = vUv.xy + vec2(x,y)/resolution;
46
+ vec2 _uv = vUv.xy + vec2(x,y) / world_size;
45
47
  float h = texture2D(heightMap, _uv).x;
46
- return vec3(_uv.x, h, _uv.y);
48
+ return vec3( _uv.x * world_size.x, h, _uv.y * world_size.y );
47
49
  }
48
50
 
49
51
  float hash1( float n )
@@ -56,92 +58,70 @@ const AmbientOcclusionShader = function () {
56
58
  p = 50.0*fract( p*0.3183099 );
57
59
  return fract( p.x*p.y*(p.x+p.y) );
58
60
  }
59
-
60
61
 
61
- float noise( in vec2 x )
62
- {
63
- vec2 p = floor(x);
64
- vec2 w = fract(x);
65
- vec2 u = w*w*w*(w*(w*6.0-15.0)+10.0);
66
-
67
- float a = hash1(p+vec2(0,0));
68
- float b = hash1(p+vec2(1,0));
69
- float c = hash1(p+vec2(0,1));
70
- float d = hash1(p+vec2(1,1));
71
-
72
- return -1.0+2.0*( a + (b-a)*u.x + (c-a)*u.y + (a - b - c + d)*u.x*u.y );
73
- }
74
-
75
- float noise_01(in vec2 x){
76
- return (noise(x) + 1.0)*0.5;
62
+ // Non-sin based hash function, fast and has good randomness
63
+ float hash12(vec2 p){
64
+ vec3 p3 = fract(vec3(p.xyx) * .1031);
65
+ p3 += dot(p3, p3.yzx + 33.33);
66
+ return fract((p3.x + p3.y) * p3.z);
77
67
  }
78
68
 
79
- vec3 sampleDirectionFromConicRay(float angle, vec3 direction, vec2 seed){
80
- float random_0 = noise_01( seed + vec2(1.123, 3.1) );
81
- float random_1 = noise_01( seed + vec2(3.123, 7.8913) );
82
-
83
-
84
- float angleCosine = cos(angle);
85
-
86
- float z = random_0 * (1.0 - angleCosine) + angleCosine;
69
+ const float bias = 0.001;
87
70
 
88
- float phi = random_1 * 6.28318530718; //2*pi
71
+ float pow2(float x){
72
+ return x*x;
73
+ }
74
+
75
+ float getOcclusion(vec3 origin, vec3 normal, vec3 hit_position){
76
+ vec3 viewDelta = hit_position - origin;
89
77
 
90
- float zSqr = z*z;
78
+ float viewDistance = length( viewDelta );
91
79
 
92
- float zSqrtNeg = sqrt(1.0 - zSqr);
80
+ float vn = dot( normal, viewDelta );
81
+ float a2 = (vn) / viewDistance - bias;
82
+ float a1 = (1.0 + pow2( viewDistance ) );
93
83
 
94
- float x = zSqrtNeg * cos(phi);
95
- float y = zSqrtNeg * sin(phi);
84
+ return max(0.0, a2) / a1;
85
+ }
96
86
 
97
- float dX = direction.x;
98
- float dY = direction.y;
99
- float dZ = direction.z;
87
+ const float PI2 = 6.28318530717958;
88
+ const float kernelRadius = 100.0;
89
+
90
+ const float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES );
91
+ const float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES );
92
+
93
+ float getAmbientOcclusion(vec3 world_position, vec3 world_normal){
94
+ // jsfiddle that shows sample pattern: https://jsfiddle.net/a16ff1p7/
95
+ float angle = hash12( vUv ) * PI2;
96
+ vec2 radius = vec2( rayLength * INV_NUM_SAMPLES );
100
97
 
101
- float rot = acos(dZ);
102
-
103
- float c = dZ;
104
- float s = sin(rot);
105
- float t = 1.0 - c;
106
- float tx = -t * dY;
107
- float ty = t * dX;
98
+ float occlusionSum = 0.0;
99
+ float weightSum = 0.0;
108
100
 
109
- float n11 = c - tx * dY;
110
- float n12 = tx * dX;
111
- float n22 = ty * dX + c;
112
- float n13 = s * dX;
113
- float n32 = -s * dY;
101
+ for( int i = 0; i < NUM_SAMPLES; i ++ ) {
102
+ vec2 sampleUv = vec2( cos( angle ), sin( angle ) ) * radius * float(i+1);
103
+
104
+ angle += ANGLE_STEP;
105
+
106
+ vec3 sample_pos = get(sampleUv.x, sampleUv.y);
114
107
 
115
- float _x = n11 * x + n12 * y + n13 * z;
116
- float _y = n12 * x + n22 * y + -n32 * z;
117
- float _z = -n13 * x + n32 * y + c * z;
108
+ occlusionSum += getOcclusion(world_position, world_normal, sample_pos);
109
+ weightSum += 1.0;
110
+ }
118
111
 
119
- return vec3(_x, _y, _z);
112
+ return occlusionSum/weightSum;
120
113
  }
121
-
114
+
122
115
  void main() {
123
- float occlusion = 0.0;
124
116
  vec3 pos = get(0.0, 0.0);
125
- vec3 normal = texture2D( normalMap, vUv ).xyz;
126
-
127
- for(int i=1; i< ${samplesPerPixel}; i++){
128
- float s = float(i)/${samplesPerPixel - 1}.0;
129
- float a = sqrt(s*512.0);
130
- float b = sqrt(s)*rayLength;
131
- float x = sin(a)*b;
132
- float y = cos(a)*b;
133
-
134
- vec3 sample_pos = get(x, y);
135
-
136
- vec3 sample_dir = normalize(sample_pos - pos);
137
- float vDot = dot(normal, sample_dir);
138
- float lambert = clamp(vDot, 0.0, 1.0);
139
- float dist_factor = 0.23/sqrt(length(sample_pos - pos));
140
- occlusion += dist_factor*lambert;
141
- }
142
117
 
143
- float incident = 1.0 - occlusion/${samplesPerPixel - 1}.0;
144
- gl_FragColor = vec4(incident, incident, incident, 1.0);
118
+ vec3 normal = texture2D( normalMap, vUv ).xzy;
119
+
120
+ float occlusion = getAmbientOcclusion(pos, normal);
121
+
122
+ float incident = 1.0 - occlusion;
123
+
124
+ gl_FragColor = vec4(pow(incident,10.0), 0.0, 0.0, 1.0);
145
125
  }
146
126
  `
147
127
  ].join('\n')
@@ -0,0 +1,119 @@
1
+ import { Vector2 } from "three";
2
+
3
+ const KERNEL_SIZE = 15;
4
+
5
+ const fragment = `
6
+ // bilateral filter, based on https://www.shadertoy.com/view/4dfGDH# and
7
+ // http://people.csail.mit.edu/sparis/bf_course/course_notes.pdf
8
+ // A bilateral filter is a non-linear, edge-preserving, and noise-reducing smoothing filter for images.
9
+ // It replaces the intensity of each pixel with a weighted average of intensity values from nearby pixels.
10
+ // This weight can be based on a Gaussian distribution. Crucially, the weights depend not only on
11
+ // Euclidean distance of pixels, but also on the radiometric differences (e.g., range differences, such
12
+ // as color intensity, depth distance, etc.). This preserves sharp edges.
13
+
14
+ float normpdf3(in vec3 v, in float sigma) {
15
+ return 0.39894 * exp(-0.5 * dot(v, v) / (sigma * sigma)) / sigma;
16
+ }
17
+
18
+ // filter size
19
+ #define MSIZE ${KERNEL_SIZE}
20
+ varying vec2 vUv;
21
+ uniform sampler2D source;
22
+ uniform vec2 pixelOffset;
23
+ uniform vec2 sigmas;
24
+ uniform float bZnorm;
25
+ uniform float kernel[MSIZE];
26
+
27
+ void main(void) {
28
+
29
+ // range sigma - controls blurriness based on a pixel distance
30
+ float sigma = sigmas.x;
31
+
32
+ // domain sigma - controls blurriness based on a pixel similarity (to preserve edges)
33
+ float bSigma = sigmas.y;
34
+ vec4 pixel = texture2D(source, vUv);
35
+ vec4 accumulatedColor = vec4(0.0);
36
+ float accumulatedFactor = 0.0;
37
+
38
+ // read out the texels
39
+ const int kSize = (MSIZE-1)/2;
40
+
41
+ for (int i = -kSize; i <= kSize; ++i) {
42
+ for (int j = -kSize; j <= kSize; ++j) {
43
+
44
+ // sample the pixel with offset
45
+ vec2 coord = vUv + vec2(float(i), float(j)) * pixelOffset;
46
+ vec4 rgba = texture2D(source, coord);
47
+
48
+ // bilateral factors
49
+ float factor = kernel[kSize + j] * kernel[kSize + i];
50
+ factor *= normpdf3(rgba.rgb - pixel.rgb, bSigma) * bZnorm;
51
+
52
+ // accumulate
53
+ accumulatedColor += factor * rgba;
54
+ accumulatedFactor += factor;
55
+ }
56
+ }
57
+
58
+ gl_FragColor = accumulatedColor / accumulatedFactor;
59
+ }
60
+ `;
61
+
62
+ /**
63
+ *
64
+ * @param {number} x
65
+ * @param {number} sigma
66
+ * @return {number}
67
+ */
68
+ function normpdf(x, sigma) {
69
+ return 0.39894 * Math.exp(-0.5 * x * x / (sigma * sigma)) / sigma;
70
+ }
71
+
72
+ /**
73
+ *
74
+ * @param {number} sigma
75
+ * @param {number} filterSmoothness
76
+ * @constructor
77
+ */
78
+ export function DenoiseShader(sigma = 10, filterSmoothness = 0.2) {
79
+ // compute zNorm
80
+ const zNorm = 1 / normpdf(0, filterSmoothness);
81
+
82
+ // build kernel
83
+ const kernel = new Float32Array(KERNEL_SIZE);
84
+ const kSize = Math.floor((KERNEL_SIZE - 1) / 2);
85
+ for (let i = 0; i <= kSize; i++) {
86
+ const v = normpdf(i, sigma);
87
+
88
+ kernel[kSize + i] = v;
89
+ kernel[kSize - i] = v;
90
+ }
91
+
92
+ return {
93
+ uniforms: {
94
+ source: {
95
+ type: 't',
96
+ value: null
97
+ },
98
+ pixelOffset: {
99
+ type: 'v2',
100
+ /**
101
+ * Holds single pixel size in UV space ( 1 / texture_size )
102
+ */
103
+ value: new Vector2(1, 1)
104
+ },
105
+ sigmas: {
106
+ type: "v2", value: new Vector2(sigma, sigma)
107
+ },
108
+ bZnorm: {
109
+ type: 'f',
110
+ value: zNorm
111
+ },
112
+ kernel: {
113
+ type: 'f',
114
+ value: kernel
115
+ }
116
+ },
117
+ fragmentShader: fragment
118
+ };
119
+ }
@@ -0,0 +1,33 @@
1
+ export function noise_octaves(
2
+ noise,
3
+ noise_ctx,
4
+ x, y, z,
5
+ octaves,
6
+ persistence = 0.5,
7
+ lacunarity = 2
8
+ ) {
9
+ let total = 0;
10
+ let frequency = 1;
11
+ let amplitude = 1;
12
+
13
+ let totalAmplitude = 0; // Used for normalizing result to 0.0 - 1.0
14
+ let i = 0;
15
+
16
+ for (; i < octaves; i++) {
17
+
18
+ const noise_value = noise.call(noise_ctx,
19
+ x * frequency,
20
+ y * frequency,
21
+ z * frequency
22
+ );
23
+
24
+ total += noise_value * amplitude;
25
+
26
+ totalAmplitude += amplitude;
27
+ amplitude *= persistence;
28
+ frequency *= lacunarity;
29
+ }
30
+
31
+ return total / totalAmplitude;
32
+ }
33
+
@@ -3,43 +3,32 @@
3
3
  */
4
4
  import { ClampToEdgeWrapping, DataTexture, RGBAFormat, UnsignedByteType } from 'three';
5
5
  import { assert } from "../../../../core/assert.js";
6
+ import { copy_Sampler2D_channel_data } from "./copy_Sampler2D_channel_data.js";
7
+ import { Sampler2D } from "./Sampler2D.js";
6
8
 
7
9
 
8
10
  /**
9
11
  *
10
12
  * @param {Sampler2D} sampler
11
- * @param {Number} scale
12
- * @param {Number} offset
13
13
  * @return {DataTexture}
14
14
  */
15
- function sampler2D2Texture(sampler, scale = 1, offset = 0) {
15
+ function sampler2D2Texture(sampler) {
16
16
  assert.notEqual(sampler, undefined, 'sampler is undefined');
17
- assert.equal(typeof scale, "number", `scale must be a number, instead was '${typeof scale}'`);
18
- assert.equal(typeof offset, "number", `scale must be a number, instead was '${typeof offset}'`);
19
17
 
20
18
  const result = new DataTexture();
21
19
  result.format = RGBAFormat;
22
20
 
23
21
  let data;
24
22
 
25
- if (scale === 1 && offset === 0 && sampler.itemSize === 4) {
23
+ if (sampler.itemSize === 4) {
26
24
  //no transformation, use a direct reference
27
25
  data = sampler.data;
28
26
 
29
27
  } else {
30
- data = new Uint8Array(sampler.width * sampler.height * 4);
31
- //slow process with mutation
32
- const arrayFiller = sampler.makeArrayFiller(scale, offset);
28
+ const dest = Sampler2D.uint8(4, sampler.width, sampler.height);
29
+ copy_Sampler2D_channel_data(sampler, dest);
33
30
 
34
- let index = 0;
35
- let x, y;
36
-
37
- for (y = 0; y < sampler.height; y++) {
38
- for (x = 0; x < sampler.width; x++) {
39
- arrayFiller(index, data, x, y);
40
- index += 4;
41
- }
42
- }
31
+ data = dest.data;
43
32
  }
44
33
 
45
34
  result.type = UnsignedByteType;
@@ -10,20 +10,23 @@ import { typedArrayConstructorByInstance } from "../typedArrayConstructorByInsta
10
10
  * @param {number} y
11
11
  * @param scale
12
12
  * @param offset
13
+ * @param {number} resolution
13
14
  */
14
- export function paintSamplerOnHTML(s, x, y, scale, offset) {
15
+ export function paintSamplerOnHTML(s, x, y, scale, offset, resolution=256) {
15
16
 
16
- const d = 180;
17
+ const d = resolution;
17
18
 
18
- const target = new Sampler2D(typedArrayConstructorByInstance(s.data), s.itemSize, d, d);
19
+ const TA = typedArrayConstructorByInstance(s.data);
20
+
21
+ const target = new Sampler2D(new TA(d * d * s.itemSize), s.itemSize, d, d);
19
22
 
20
23
  scaleSampler2D(s, target);
21
24
 
22
25
  const c = convertSampler2D2Canvas(target, scale, offset);
23
26
  c.style.zIndex = 1000;
24
27
  c.style.position = "absolute";
25
- c.style.left = `${x * d}px`;
26
- c.style.top = `${y * d}px`;
28
+ c.style.left = `${x}px`;
29
+ c.style.top = `${y}px`;
27
30
 
28
31
  document.body.appendChild(c);
29
32
  }
@@ -1,5 +1,8 @@
1
- import { ClampToEdgeWrapping, DataTexture, FloatType, LinearFilter, NearestFilter, RedFormat } from "three";
1
+ import { ClampToEdgeWrapping, DataTexture, FloatType, LinearFilter, NearestFilter } from "three";
2
2
  import { assert } from "../../../../core/assert.js";
3
+ import { channelCountToThreeTextureFormat } from "../channelCountToThreeTextureFormat.js";
4
+ import { computeThreeTextureInternalFormatFromDataType } from "../../render/forward_plus/data/computeThreeTextureInternalFormatFromDataType.js";
5
+ import { DataType } from "../../../../core/collection/table/DataType.js";
3
6
 
4
7
  /**
5
8
  *
@@ -12,7 +15,7 @@ export function sampler2DtoFloat32Texture(sampler) {
12
15
  const width = sampler.width;
13
16
  const height = sampler.height;
14
17
 
15
- const texture = new DataTexture(sampler.data, width, height, RedFormat, FloatType);
18
+ const texture = new DataTexture(sampler.data, width, height);
16
19
 
17
20
  texture.wrapS = ClampToEdgeWrapping;
18
21
  texture.wrapT = ClampToEdgeWrapping;
@@ -24,7 +27,11 @@ export function sampler2DtoFloat32Texture(sampler) {
24
27
 
25
28
  texture.flipY = false;
26
29
 
27
- texture.internalFormat = 'R32F';
30
+ texture.type = FloatType;
31
+ texture.format = channelCountToThreeTextureFormat(sampler.itemSize);
32
+ texture.internalFormat = computeThreeTextureInternalFormatFromDataType(DataType.Float32, sampler.itemSize);
33
+
34
+ texture.needsUpdate = true;
28
35
 
29
36
  return texture;
30
37
  }
@@ -24,7 +24,8 @@ export const SampleGroundMoistureFilter = CellFilterAdd.from(
24
24
  CellFilterStep.from(CellFilterLiteralFloat.from(0), fReadHeight)
25
25
  ),
26
26
  7,
27
- 7
27
+ 7,
28
+ 1
28
29
  ),
29
30
  CellFilterLiteralFloat.from(0.6)
30
31
  )
@@ -1,9 +1,23 @@
1
1
  import { CellFilter } from "../../CellFilter.js";
2
2
  import { gaussian } from "../../../../core/math/gaussian.js";
3
-
3
+ import { max2 } from "../../../../core/math/max2.js";
4
+ import { assert } from "../../../../core/assert.js";
5
+
6
+ /**
7
+ *
8
+ * @param {number[]} result
9
+ * @param {number} samplesX
10
+ * @param {number} samplesY
11
+ * @param {number} sigma_x
12
+ * @param {number} sigma_y
13
+ * @return {number}
14
+ */
4
15
  function buildKernel(result, samplesX, samplesY, sigma_x, sigma_y) {
5
- const half_samples_x = (samplesX / 2);
6
- const half_samples_y = (samplesY / 2);
16
+ assert.isNonNegativeInteger(samplesX, 'samplesX');
17
+ assert.isNonNegativeInteger(samplesY, 'samplesY');
18
+
19
+ const half_samples_x = samplesX * 0.5;
20
+ const half_samples_y = samplesY * 0.5;
7
21
 
8
22
  let powerTotal = 0;
9
23
  let ix, iy;
@@ -70,6 +84,8 @@ export class CellFilterGaussianBlur extends CellFilter {
70
84
  if (!this.source.initialized) {
71
85
  this.source.initialize(grid, seed);
72
86
 
87
+ this.__kernel = new Float32Array(this.samples_x * this.samples_y);
88
+
73
89
  // initialize kernel
74
90
  this.__kernel_total_power = buildKernel(this.__kernel, this.samples_x, this.samples_y, this.sigma_x, this.sigma_y);
75
91
  }
@@ -84,13 +100,13 @@ export class CellFilterGaussianBlur extends CellFilter {
84
100
  * @param {number} quality
85
101
  * @returns {CellFilterGaussianBlur}
86
102
  */
87
- static from(source, x, y, quality = 5) {
103
+ static from(source, x, y, quality = 3) {
88
104
  const r = new CellFilterGaussianBlur();
89
105
 
90
106
  r.source = source;
91
107
 
92
- r.samples_x = quality * x;
93
- r.samples_y = quality * y;
108
+ r.samples_x = max2(2, Math.round(quality * x));
109
+ r.samples_y = max2(2, Math.round(quality * y));
94
110
 
95
111
  r.size_x = x;
96
112
  r.size_y = y;
@@ -102,24 +118,24 @@ export class CellFilterGaussianBlur extends CellFilter {
102
118
  const samplesX = this.samples_x;
103
119
  const samplesY = this.samples_y;
104
120
 
105
- const half_samples_x = (samplesX / 2);
106
- const half_samples_y = (samplesY / 2);
121
+ const half_samples_x = (samplesX - 1) * 0.5;
122
+ const half_samples_y = (samplesY - 1) * 0.5;
107
123
 
108
124
  const sizeX = this.size_x;
109
125
  const sizeY = this.size_y;
110
126
 
111
- const local_u_scale = 1 / samplesX;
112
- const local_v_scale = 1 / samplesY;
127
+ const local_u_scale = 1 / (samplesX - 1);
128
+ const local_v_scale = 1 / (samplesY - 1);
113
129
 
114
130
  let sum = 0;
115
- let ix, iy;
131
+ let ix, iy = 0;
116
132
 
117
133
  const source = this.source;
118
134
  const kernel = this.__kernel;
119
135
 
120
- const i_samples_x = samplesX|0;
136
+ const i_samples_x = samplesX | 0;
121
137
 
122
- for (iy = 0; iy < samplesY; iy++) {
138
+ for (; iy < samplesY; iy++) {
123
139
  const local_y = iy - half_samples_y;
124
140
 
125
141
  const ny = local_y * local_v_scale;
@@ -2,6 +2,7 @@ import { assert } from "../../core/assert.js";
2
2
  import { QuadTreeNode } from "../../core/geom/2d/quad-tree/QuadTreeNode.js";
3
3
  import { OffsetScaleTransform2D } from "../../engine/ecs/terrain/ecs/OffsetScaleTransform2D.js";
4
4
  import { MarkerMatchCounter } from "./MarkerMatchCounter.js";
5
+ import { max2 } from "../../core/math/max2.js";
5
6
 
6
7
  const scratch_match_counter = new MarkerMatchCounter();
7
8
 
@@ -167,8 +168,14 @@ export class GridData {
167
168
  * @param {number} height
168
169
  */
169
170
  resize(width, height) {
170
- assert.typeOf(width, 'number', 'width');
171
- assert.typeOf(height, 'number', 'height');
171
+ assert.isNumber(width, 'width');
172
+ assert.isNumber(height, 'height');
173
+
174
+ assert.isNonNegativeInteger(width,'width');
175
+ assert.isNonNegativeInteger(height,'height');
176
+
177
+ assert.isFiniteNumber(width, 'width');
178
+ assert.isFiniteNumber(height, 'width');
172
179
 
173
180
  if (width === this.width && height === this.height) {
174
181
  //no need, already the right size
@@ -187,6 +194,14 @@ export class GridData {
187
194
 
188
195
  layer.resize(width, height);
189
196
  }
197
+
198
+ // resize the quad tree pre-emptively, to avoid expensive re-balancing operations later on
199
+ this.markers.resize(
200
+ -max2(1, width * 0.5),
201
+ -max2(1, height * 0.5),
202
+ max2(width + 1, width * 1.5),
203
+ max2(height + 1, height * 1.5)
204
+ );
190
205
  }
191
206
 
192
207
  /**
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "productName": "Meep",
6
6
  "description": "production-ready JavaScript game engine based on Entity Component System Architecture",
7
7
  "author": "Alexander Goldring",
8
- "version": "2.33.65",
8
+ "version": "2.33.67",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",