@woosh/meep-engine 2.33.55 → 2.33.59

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.
@@ -2,7 +2,7 @@ import { UnitCubeShape3D } from "../UnitCubeShape3D.js";
2
2
  import { UnitSphereShape3D } from "../UnitSphereShape3D.js";
3
3
  import { TransformedShape3D } from "../TransformedShape3D.js";
4
4
  import { UnionShape3D } from "../UnionShape3D.js";
5
- import { shape_from_json} from "./shape_from_json.js";
5
+ import { shape_from_json } from "./shape_from_json.js";
6
6
  import { shape_to_json } from "./shape_to_json.js";
7
7
 
8
8
  export const type_adapters = {
@@ -13,7 +13,6 @@ import { isValueBetweenInclusive } from "./isValueBetweenInclusive.js";
13
13
  import { solveQuadratic } from "./solveQuadratic.js";
14
14
  import { overlap1D } from "./overlap1D.js";
15
15
  import { computeHashFloat } from "./hash/computeHashFloat.js";
16
- import { computeHashFloatArray } from "./hash/computeHashFloatArray.js";
17
16
 
18
17
  test('min2', () => {
19
18
  expect(min2(0, 0)).toBe(0);
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import Vector2 from '../../../core/geom/Vector2.js';
5
5
  import ObservedBoolean from "../../../core/model/ObservedBoolean.js";
6
- import { BinaryClassSerializationAdapter } from "../storage/binary/BinaryClassSerializationAdapter.js";
7
6
  import { COMPONENT_SERIALIZATION_TRANSIENT_FIELD } from "../storage/COMPONENT_SERIALIZATION_TRANSIENT_FIELD.js";
8
7
  import { assert } from "../../../core/assert.js";
9
8
  import { objectDeepEquals } from "../../../core/model/object/objectDeepEquals.js";
@@ -238,108 +237,3 @@ GUIElement.serializable = true;
238
237
  export default GUIElement;
239
238
 
240
239
 
241
- export class GUIElementSerializationAdapter extends BinaryClassSerializationAdapter {
242
- constructor() {
243
- super();
244
-
245
- this.klass = GUIElement;
246
- this.version = 0;
247
- }
248
-
249
- /**
250
- *
251
- * @param {BinaryBuffer} buffer
252
- * @param {GUIElement} value
253
- */
254
- serialize(buffer, value) {
255
-
256
- if (!value.getFlag(GUIElementFlag.Managed)) {
257
- throw new Error(`Expected 'Managed' flag to be set, instead it was cleared. Cannot serialize un-managed component`);
258
- }
259
-
260
- value.anchor.toBinaryBufferFloat32(buffer);
261
- buffer.writeUTF8String(value.group);
262
-
263
- buffer.writeUint8(value.visible.getValue() ? 1 : 0);
264
-
265
- buffer.writeUTF8String(value.klass);
266
- //write parameters
267
-
268
- const paramKeys = Object.keys(value.parameters);
269
-
270
- const numParamKeys = paramKeys.length;
271
- buffer.writeUintVar(numParamKeys);
272
-
273
- for (let i = 0; i < numParamKeys; i++) {
274
- const paramKey = paramKeys[i];
275
-
276
- buffer.writeUTF8String(paramKey);
277
-
278
- const parameter = value.parameters[paramKey];
279
-
280
- const paramType = typeof parameter;
281
-
282
- if (paramType === "number") {
283
- buffer.writeUint8(1);
284
-
285
- buffer.writeFloat64(parameter);
286
- } else if (paramType === "boolean") {
287
- buffer.writeUint8(2);
288
-
289
- buffer.writeUint8(parameter ? 1 : 0);
290
- } else if (paramType === "string") {
291
- buffer.writeUint8(3);
292
-
293
- buffer.writeUTF8String(parameter);
294
- } else {
295
- throw new TypeError(`Unsupported parameter type ${paramType}`);
296
- }
297
-
298
- }
299
- }
300
-
301
- /**
302
- *
303
- * @param {BinaryBuffer} buffer
304
- * @param {GUIElement} value
305
- */
306
- deserialize(buffer, value) {
307
- value.anchor.fromBinaryBufferFloat32(buffer);
308
-
309
- value.group = buffer.readUTF8String();
310
-
311
- value.visible.set(buffer.readUint8() !== 0);
312
-
313
- value.klass = buffer.readUTF8String();
314
-
315
- const numParamKeys = buffer.readUintVar();
316
-
317
- const params = {};
318
-
319
- for (let i = 0; i < numParamKeys; i++) {
320
- //read key
321
- const paramKey = buffer.readUTF8String();
322
-
323
- //read type flag
324
- const paramTypeHeader = buffer.readUint8();
325
-
326
- let paramValue;
327
- if (paramTypeHeader === 1) {
328
- //number
329
- paramValue = buffer.readFloat64();
330
- } else if (paramTypeHeader === 2) {
331
- //boolean
332
- paramValue = buffer.readUint8() !== 0;
333
- } else if (paramTypeHeader === 3) {
334
- //string
335
- paramValue = buffer.readUTF8String();
336
- } else {
337
- throw new TypeError(`Unknown type header value '${paramTypeHeader}' for parameter key '${paramKey}'`);
338
- }
339
-
340
- params[paramKey] = paramValue;
341
- }
342
-
343
- value.parameters = params;
344
- }
345
- }
@@ -0,0 +1,108 @@
1
+ import { BinaryClassSerializationAdapter } from "../storage/binary/BinaryClassSerializationAdapter.js";
2
+ import GUIElement, { GUIElementFlag } from "./GUIElement.js";
3
+
4
+ export class GUIElementSerializationAdapter extends BinaryClassSerializationAdapter {
5
+ constructor() {
6
+ super();
7
+
8
+ this.klass = GUIElement;
9
+ this.version = 0;
10
+ }
11
+
12
+ /**
13
+ *
14
+ * @param {BinaryBuffer} buffer
15
+ * @param {GUIElement} value
16
+ */
17
+ serialize(buffer, value) {
18
+
19
+ if (!value.getFlag(GUIElementFlag.Managed)) {
20
+ throw new Error(`Expected 'Managed' flag to be set, instead it was cleared. Cannot serialize un-managed component`);
21
+ }
22
+
23
+ value.anchor.toBinaryBufferFloat32(buffer);
24
+ buffer.writeUTF8String(value.group);
25
+
26
+ buffer.writeUint8(value.visible.getValue() ? 1 : 0);
27
+
28
+ buffer.writeUTF8String(value.klass);
29
+ //write parameters
30
+
31
+ const paramKeys = Object.keys(value.parameters);
32
+
33
+ const numParamKeys = paramKeys.length;
34
+ buffer.writeUintVar(numParamKeys);
35
+
36
+ for (let i = 0; i < numParamKeys; i++) {
37
+ const paramKey = paramKeys[i];
38
+
39
+ buffer.writeUTF8String(paramKey);
40
+
41
+ const parameter = value.parameters[paramKey];
42
+
43
+ const paramType = typeof parameter;
44
+
45
+ if (paramType === "number") {
46
+ buffer.writeUint8(1);
47
+
48
+ buffer.writeFloat64(parameter);
49
+ } else if (paramType === "boolean") {
50
+ buffer.writeUint8(2);
51
+
52
+ buffer.writeUint8(parameter ? 1 : 0);
53
+ } else if (paramType === "string") {
54
+ buffer.writeUint8(3);
55
+
56
+ buffer.writeUTF8String(parameter);
57
+ } else {
58
+ throw new TypeError(`Unsupported parameter type ${paramType}`);
59
+ }
60
+
61
+ }
62
+ }
63
+
64
+ /**
65
+ *
66
+ * @param {BinaryBuffer} buffer
67
+ * @param {GUIElement} value
68
+ */
69
+ deserialize(buffer, value) {
70
+ value.anchor.fromBinaryBufferFloat32(buffer);
71
+
72
+ value.group = buffer.readUTF8String();
73
+
74
+ value.visible.set(buffer.readUint8() !== 0);
75
+
76
+ value.klass = buffer.readUTF8String();
77
+
78
+ const numParamKeys = buffer.readUintVar();
79
+
80
+ const params = {};
81
+
82
+ for (let i = 0; i < numParamKeys; i++) {
83
+ //read key
84
+ const paramKey = buffer.readUTF8String();
85
+
86
+ //read type flag
87
+ const paramTypeHeader = buffer.readUint8();
88
+
89
+ let paramValue;
90
+ if (paramTypeHeader === 1) {
91
+ //number
92
+ paramValue = buffer.readFloat64();
93
+ } else if (paramTypeHeader === 2) {
94
+ //boolean
95
+ paramValue = buffer.readUint8() !== 0;
96
+ } else if (paramTypeHeader === 3) {
97
+ //string
98
+ paramValue = buffer.readUTF8String();
99
+ } else {
100
+ throw new TypeError(`Unknown type header value '${paramTypeHeader}' for parameter key '${paramKey}'`);
101
+ }
102
+
103
+ params[paramKey] = paramValue;
104
+ }
105
+
106
+ value.parameters = params;
107
+ }
108
+ }
@@ -25,4 +25,8 @@ export class Transform {
25
25
  clone(): Transform
26
26
 
27
27
  equals(other: Transform): void
28
+
29
+ toMatrix4(result: ArrayLike<number>): void
30
+
31
+ fromMatrix4(matrix: ArrayLike<number>): void
28
32
  }
@@ -1,4 +1,13 @@
1
- import { AddEquation, CustomBlending, Mesh, OneFactor, OneMinusSrcAlphaFactor, ShaderMaterial } from "three";
1
+ import {
2
+ AddEquation,
3
+ CustomBlending,
4
+ Mesh,
5
+ OneFactor,
6
+ OneMinusSrcAlphaFactor,
7
+ RedFormat,
8
+ RGBAFormat,
9
+ ShaderMaterial
10
+ } from "three";
2
11
  import Vector2 from "../../../core/geom/Vector2.js";
3
12
  import { assert } from "../../../core/assert.js";
4
13
  import { blendingType2three } from "../texture/sampler/blendingType2Three.js";
@@ -201,10 +210,21 @@ export class CompositLayer {
201
210
  }
202
211
  };
203
212
 
213
+ // determine swizzle for the material
214
+ let swizzle;
215
+ const texture_format = this.renderTarget.texture.format;
216
+ if (texture_format === RedFormat) {
217
+ swizzle = ['r', 'r', 'r', 1];
218
+ } else if (texture_format === RGBAFormat) {
219
+ swizzle = ['r', 'g', 'b', 'a'];
220
+ } else {
221
+ throw new Error('Unsupported format');
222
+ }
223
+
204
224
  const material = new ShaderMaterial({
205
225
  uniforms,
206
226
  vertexShader: ScreenSpaceQuadShader.vertexShader(),
207
- fragmentShader: ScreenSpaceQuadShader.fragmentShader(),
227
+ fragmentShader: ScreenSpaceQuadShader.fragmentShader(swizzle),
208
228
  lights: false,
209
229
  fog: false,
210
230
  depthTest: false,
@@ -11,7 +11,6 @@ import { PI2 } from "../../../../../core/math/MathUtils.js";
11
11
 
12
12
  const m4 = new Matrix4();
13
13
  const euler = new Euler();
14
- const scratch_vector3 = new Vector3();
15
14
 
16
15
  const panOffset = new Vector3();
17
16
 
@@ -3,6 +3,14 @@ import Vector3 from "../../../../../core/geom/Vector3.js";
3
3
  import { computeTopDownTransform } from "./TopDownCameraControllerSystem.js";
4
4
  import { Transform } from "../../../../ecs/transform/Transform.js";
5
5
 
6
+ test('setFromEyeAndTarget does not introduce roll when UP is aligned with +Y', () => {
7
+ const ut = new TopDownCameraController();
8
+
9
+ ut.setFromEyeAndTarget(new Vector3(0, 1, -1), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
10
+
11
+ expect(ut.roll).toBe(0);
12
+ });
13
+
6
14
  test('setFromEyeAndTarget results in roll respecting UP vector', () => {
7
15
  const eye = new Vector3();
8
16
  const target = new Vector3();
@@ -1,5 +1,7 @@
1
1
  import Engine from "../../../Engine";
2
+ import {System} from "../../../ecs/System";
3
+ import {PathDisplay} from "./PathDisplay";
2
4
 
3
- export class PathDisplaySystem {
5
+ export class PathDisplaySystem extends System<PathDisplay> {
4
6
  constructor(engine: Engine)
5
7
  }
@@ -28,27 +28,11 @@ backGeometry.viewDir = geometry.viewDir;
28
28
 
29
29
  vLightFront = vec3( 0.0 );
30
30
  vIndirectFront = vec3( 0.0 );
31
- #ifdef DOUBLE_SIDED
32
- vLightBack = vec3( 0.0 );
33
- vIndirectBack = vec3( 0.0 );
34
- #endif
35
31
 
36
32
  IncidentLight directLight;
37
33
  float dotNL;
38
34
  vec3 directLightColor_Diffuse;
39
35
 
40
- vIndirectFront += getAmbientLightIrradiance( ambientLightColor );
41
-
42
- vIndirectFront += getLightProbeIrradiance( lightProbe, geometry.normal );
43
-
44
- #ifdef DOUBLE_SIDED
45
-
46
- vIndirectBack += getAmbientLightIrradiance( ambientLightColor );
47
-
48
- vIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry.normal );
49
-
50
- #endif
51
-
52
36
  #if NUM_POINT_LIGHTS > 0
53
37
 
54
38
  #pragma unroll_loop_start
@@ -56,17 +40,8 @@ vIndirectFront += getLightProbeIrradiance( lightProbe, geometry.normal );
56
40
 
57
41
  getPointLightInfo( pointLights[ i ], geometry, directLight );
58
42
 
59
- dotNL = dot( geometry.normal, directLight.direction );
60
- directLightColor_Diffuse = directLight.color;
61
-
62
- vLightFront += saturate( dotNL ) * directLightColor_Diffuse;
63
-
64
- #ifdef DOUBLE_SIDED
65
-
66
- vLightBack += saturate( - dotNL ) * directLightColor_Diffuse;
67
-
68
- #endif
69
-
43
+ RE_Direct( directLight, geometry, material, reflectedLight );
44
+
70
45
  }
71
46
  #pragma unroll_loop_end
72
47
 
@@ -79,16 +54,7 @@ vIndirectFront += getLightProbeIrradiance( lightProbe, geometry.normal );
79
54
 
80
55
  getSpotLightInfo( spotLights[ i ], geometry, directLight );
81
56
 
82
- dotNL = dot( geometry.normal, directLight.direction );
83
- directLightColor_Diffuse = directLight.color;
84
-
85
- vLightFront += saturate( dotNL ) * directLightColor_Diffuse;
86
-
87
- #ifdef DOUBLE_SIDED
88
-
89
- vLightBack += saturate( - dotNL ) * directLightColor_Diffuse;
90
-
91
- #endif
57
+ RE_Direct( directLight, geometry, material, reflectedLight );
92
58
  }
93
59
  #pragma unroll_loop_end
94
60
 
@@ -101,38 +67,50 @@ vIndirectFront += getLightProbeIrradiance( lightProbe, geometry.normal );
101
67
 
102
68
  getDirectionalLightInfo( directionalLights[ i ], geometry, directLight );
103
69
 
104
- dotNL = dot( geometry.normal, directLight.direction );
105
- directLightColor_Diffuse = directLight.color;
106
-
107
- vLightFront += saturate( dotNL ) * directLightColor_Diffuse;
108
-
109
- #ifdef DOUBLE_SIDED
110
-
111
- vLightBack += saturate( - dotNL ) * directLightColor_Diffuse;
112
-
113
- #endif
70
+ RE_Direct( directLight, geometry, material, reflectedLight );
114
71
 
115
72
  }
116
73
  #pragma unroll_loop_end
117
74
 
118
75
  #endif
119
76
 
120
- #if NUM_HEMI_LIGHTS > 0
121
77
 
122
- #pragma unroll_loop_start
123
- for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
124
78
 
125
- vIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );
79
+ #if defined( RE_IndirectDiffuse )
80
+ vec3 iblIrradiance = vec3( 0.0 );
81
+ vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
82
+ irradiance += getLightProbeIrradiance( lightProbe, geometry.normal );
83
+ #if NUM_HEMI_LIGHTS > 0
126
84
 
127
- #ifdef DOUBLE_SIDED
128
-
129
- vIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry.normal );
85
+ #pragma unroll_loop_start
86
+ for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
87
+
88
+ vIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal );
89
+
90
+ }
91
+ #pragma unroll_loop_end
92
+
93
+ #endif
94
+
95
+ #if defined( USE_ENVMAP ) && defined( ENVMAP_TYPE_CUBE_UV )
96
+ iblIrradiance += getIBLIrradiance( geometry.normal );
97
+ #endif
98
+ #endif
130
99
 
131
- #endif
100
+ #if defined( RE_IndirectSpecular )
101
+ vec3 radiance = vec3( 0.0 );
102
+ vec3 clearcoatRadiance = vec3( 0.0 );
103
+ #endif
132
104
 
133
- }
134
- #pragma unroll_loop_end
105
+ #if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )
106
+ radiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );
107
+ #endif
135
108
 
109
+ #if defined( RE_IndirectDiffuse )
110
+ RE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );
111
+ #endif
112
+ #if defined( RE_IndirectSpecular )
113
+ RE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );
136
114
  #endif
137
115
  `;
138
116
 
@@ -389,18 +367,12 @@ function make_fragment_shader() {
389
367
 
390
368
  vec3 transformedNormal = construct_spherical_normal_from_point_coord(gl_PointCoord.xy);
391
369
 
392
- ${CHUNK_LAMBERT_LIGHTING}
393
-
394
370
  LambertMaterial material = LambertMaterial(texel.rgb);
395
-
396
371
  ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
397
- vec3 totalEmissiveRadiance = vec3(0.0);
398
-
399
- reflectedLight.indirectDiffuse += vIndirectFront;
400
- reflectedLight.indirectDiffuse *= BRDF_Lambert( texel.rgb );
401
372
 
402
- reflectedLight.directDiffuse = vLightFront;
403
- reflectedLight.directDiffuse *= BRDF_Lambert( texel.rgb ); // shadowmask goes here
373
+ ${CHUNK_LAMBERT_LIGHTING}
374
+
375
+ vec3 totalEmissiveRadiance = vec3(0.0);
404
376
 
405
377
  //@injection_point FORWARD_PLUS:accumulation
406
378
 
@@ -169,7 +169,7 @@ export class AttributeValue {
169
169
  throw new Error(`Expected 'value' to be an array, instead got something else (=${value})`);
170
170
  }
171
171
  } else {
172
- this.__value = [];
172
+ // retain current value
173
173
  }
174
174
 
175
175
  if (lookup !== undefined) {
@@ -19,6 +19,8 @@ export class ParticleVolume {
19
19
 
20
20
  from(args?: Args): void
21
21
 
22
+ fromJSON(json: any): void
23
+
22
24
  build(): void
23
25
 
24
26
  readonly attributes: Attributes
@@ -257,8 +257,10 @@ export class ParticleVolume {
257
257
  init_attributes() {
258
258
  const spec = PARTICULAR_PARTICLE_SPECIFICATION;
259
259
 
260
- for (let i = 0; i < spec.attributes.length; i++) {
261
- const attributeSpec = spec.attributes[i];
260
+ const attributeSpecs = spec.attributes;
261
+ const attribute_count = attributeSpecs.length;
262
+ for (let i = 0; i < attribute_count; i++) {
263
+ const attributeSpec = attributeSpecs[i];
262
264
 
263
265
  const av = new AttributeValue();
264
266
  av.spec = attributeSpec;
@@ -300,6 +302,7 @@ export class ParticleVolume {
300
302
  * @param {number} [particle_size]
301
303
  * @param {AbstractShape3D} [shape]
302
304
  * @param {boolean} sort
305
+ * @param {boolean} lighting
303
306
  * @param {string} [sprite]
304
307
  */
305
308
  from({
@@ -307,14 +310,19 @@ export class ParticleVolume {
307
310
  particle_size = 1,
308
311
  shape = new UnitCubeShape3D(),
309
312
  sort = false,
313
+ lighting = true,
310
314
  sprite = "data/textures/particle/smokeparticle.png"
311
315
  } = {}) {
312
316
 
313
317
  this.__density = density;
314
318
  this.__shape = shape;
315
319
 
316
- this.__emitter.writeFlag(ParticleEmitterFlag.DepthSorting, sort);
317
- this.__emitter.layers.get(0).imageURL = sprite;
320
+ const emitter = this.__emitter;
321
+
322
+ emitter.writeFlag(ParticleEmitterFlag.DepthSorting, sort);
323
+ emitter.writeFlag(ParticleEmitterFlag.Lit, lighting);
324
+
325
+ emitter.layers.get(0).imageURL = sprite;
318
326
 
319
327
  const attributes = this.attributes;
320
328
 
@@ -328,6 +336,7 @@ export class ParticleVolume {
328
336
  * @param {number} [particle_size]
329
337
  * @param {Object} shape
330
338
  * @param {boolean} [is_sorted]
339
+ * @param {boolean} [is_lit]
331
340
  * @param {string} sprite
332
341
  * @param {Object<Object>} [attributes]
333
342
  */
@@ -337,6 +346,7 @@ export class ParticleVolume {
337
346
  particle_size = 1,
338
347
  shape,
339
348
  is_sorted = true,
349
+ is_lit = true,
340
350
  sprite,
341
351
  attributes = {}
342
352
  }) {
@@ -350,9 +360,11 @@ export class ParticleVolume {
350
360
  shape: shape_object,
351
361
  particle_size,
352
362
  sort: is_sorted,
363
+ lighting: is_lit,
353
364
  sprite
354
365
  });
355
366
 
367
+ // parse attributes
356
368
  for (const attributesKey in attributes) {
357
369
  const attribute_object = this.attributes[attributesKey];
358
370
 
@@ -397,13 +409,13 @@ export class ParticleVolume {
397
409
 
398
410
  const layer = emitter.layers.get(0);
399
411
 
412
+ // build transform matrix
400
413
  const m4_transform = new Matrix4();
401
414
 
402
415
  const t = this.__transform;
403
416
 
404
417
  composeMatrix4(m4_transform, t.position, t.rotation, t.scale);
405
418
 
406
-
407
419
  const m4_layer = new Matrix4();
408
420
 
409
421
  composeMatrix4(m4_layer, layer.position, new Quaternion(), layer.scale);
@@ -414,11 +426,14 @@ export class ParticleVolume {
414
426
 
415
427
  m4_final.multiplyMatrices(m4_transform, m4_layer);
416
428
 
417
- for (let i = 0; i < particle_count; i++) {
429
+ // cache variables to save a bit on de-referencing
430
+ const matrix_elements = m4_final.elements;
431
+ const shape = this.__shape;
418
432
 
419
- this.__shape.sample_random_point_in_volume(v3_position, 0, random);
433
+ for (let i = 0; i < particle_count; i++) {
434
+ shape.sample_random_point_in_volume(v3_position, 0, random);
420
435
 
421
- vec3.transformMat4(v3_position, v3_position, m4_final.elements);
436
+ vec3.transformMat4(v3_position, v3_position, matrix_elements);
422
437
 
423
438
  particles.writeAttributeVector3(i, PARTICLE_ATTRIBUTE_POSITION, v3_position[0], v3_position[1], v3_position[2]);
424
439
  }
@@ -1,9 +1,9 @@
1
- import { NoBlending, ShaderMaterial, UniformsUtils, Vector2, WebGLMultisampleRenderTarget } from "three";
1
+ import { NoBlending, RGBAFormat, ShaderMaterial, UniformsUtils, Vector2, WebGLMultisampleRenderTarget } from "three";
2
2
  import { BlurShaderUtils, DepthLimitedBlurShader } from "three/examples/jsm/shaders/DepthLimitedBlurShader.js";
3
3
  import { renderScreenSpace } from "../../utils/renderScreenSpace.js";
4
4
 
5
5
  export class DepthLimitedBlur {
6
- constructor() {
6
+ constructor({ format = RGBAFormat, radius = 8 }) {
7
7
 
8
8
  /**
9
9
  *
@@ -19,7 +19,7 @@ export class DepthLimitedBlur {
19
19
  */
20
20
  this.__rt_output = null;
21
21
 
22
- this.__intermediate = new WebGLMultisampleRenderTarget({});
22
+ this.__intermediate = new WebGLMultisampleRenderTarget({ format });
23
23
 
24
24
 
25
25
  this.__material_blur_x = new ShaderMaterial({
@@ -41,7 +41,7 @@ export class DepthLimitedBlur {
41
41
 
42
42
  this.__depth_cutoff = 0.01;
43
43
 
44
- this.__blur_radius = 8;
44
+ this.__blur_radius = radius;
45
45
  this.__blur_std_dev = 4;
46
46
  }
47
47
 
@@ -161,4 +161,8 @@ export class DepthLimitedBlur {
161
161
  this.__rt_output = render_target;
162
162
 
163
163
  }
164
+
165
+ dispose() {
166
+ this.__intermediate.dispose();
167
+ }
164
168
  }
@@ -1,8 +1,9 @@
1
1
  import { EnginePlugin } from "../../../../../plugin/EnginePlugin.js";
2
2
  import {
3
+ GLSL3,
3
4
  LinearFilter,
4
5
  NoBlending,
5
- RGBAFormat,
6
+ RedFormat,
6
7
  ShaderMaterial,
7
8
  UniformsUtils,
8
9
  WebGLMultisampleRenderTarget
@@ -25,9 +26,18 @@ export class AmbientOcclusionPostProcessEffect extends EnginePlugin {
25
26
  defines: defines,
26
27
  fragmentShader: SAOShader.fragmentShader,
27
28
  vertexShader: SAOShader.vertexShader,
28
- uniforms: UniformsUtils.clone(SAOShader.uniforms)
29
+ uniforms: UniformsUtils.clone(SAOShader.uniforms),
30
+ glslVersion: GLSL3
29
31
  });
30
32
 
33
+ // AO parameters
34
+ const uniforms = this.__material.uniforms;
35
+ uniforms.bias.value = 0.5;
36
+ uniforms.intensity.value = 2;
37
+
38
+ uniforms.kernelRadius.value = 30;
39
+ uniforms.minResolution.value = 0;
40
+
31
41
  this.__material.blending = NoBlending;
32
42
 
33
43
  this.__material.extensions.derivatives = true;
@@ -43,7 +53,7 @@ export class AmbientOcclusionPostProcessEffect extends EnginePlugin {
43
53
  defines.NUM_SAMPLES = 13;
44
54
 
45
55
  // More rings = better quality
46
- defines.NUM_RINGS = 4;
56
+ defines.NUM_RINGS = 5;
47
57
 
48
58
  /**
49
59
  * Should normal buffer be used or not? Using normal buffer might be significantly more expensive, as this would require normal buffer to be built every frame. If there are other users of the buffer - this is not an issue
@@ -78,10 +88,10 @@ export class AmbientOcclusionPostProcessEffect extends EnginePlugin {
78
88
  minFilter: LinearFilter,
79
89
  magFilter: LinearFilter,
80
90
 
81
- format: RGBAFormat,
91
+ format: RedFormat,
82
92
  depthBuffer: false,
83
93
  stencilBuffer: false,
84
- generateMipmaps: false
94
+ generateMipmaps: false,
85
95
  });
86
96
 
87
97
  /**
@@ -96,7 +106,7 @@ export class AmbientOcclusionPostProcessEffect extends EnginePlugin {
96
106
  * @type {DepthLimitedBlur}
97
107
  * @private
98
108
  */
99
- this.__blur = new DepthLimitedBlur();
109
+ this.__blur = new DepthLimitedBlur({ format: RedFormat });
100
110
  }
101
111
 
102
112
  /**
@@ -156,16 +166,6 @@ export class AmbientOcclusionPostProcessEffect extends EnginePlugin {
156
166
  uniforms.cameraInverseProjectionMatrix.value.copy(camera.projectionMatrixInverse);
157
167
  uniforms.cameraProjectionMatrix.value.copy(camera.projectionMatrix);
158
168
 
159
- // AO parameters
160
- uniforms.bias.value = 0.5;
161
- uniforms.intensity.value = 0.18;
162
-
163
- // scale is divided by camera far in the shader, we unbias it here
164
- // TODO remove this term from the shader
165
- uniforms.scale.value = far * 0.25;
166
-
167
- uniforms.kernelRadius.value = 30;
168
- uniforms.minResolution.value = 0;
169
169
 
170
170
  // trigger uniform update
171
171
  material.uniformsNeedUpdate = true;
@@ -300,7 +300,12 @@ export class AmbientOcclusionPostProcessEffect extends EnginePlugin {
300
300
  const graphics = engine.graphics;
301
301
 
302
302
  // bind compositing layer
303
- this.__composit_layer = graphics.layerComposer.createLayer({}, BlendingType.Multiply);
303
+ this.__composit_layer = graphics.layerComposer.createLayer(
304
+ {
305
+ format: RedFormat
306
+ },
307
+ BlendingType.Multiply
308
+ );
304
309
  this.__composit_layer.name = 'Ambient Occlusion';
305
310
 
306
311
  // apply ambient occlusion on opaque pass, otherwise it would occlude transparent geometry
@@ -344,6 +349,8 @@ export class AmbientOcclusionPostProcessEffect extends EnginePlugin {
344
349
  this.__composit_layer.on.preRender.remove(this.__render, this);
345
350
  graphics.viewport.size.onChanged.add(this.__update_render_target_size, this);
346
351
 
352
+ this.__blur.dispose();
353
+
347
354
  return super.shutdown();
348
355
  }
349
356
 
@@ -16,7 +16,6 @@ const SAOShader = {
16
16
  uniforms: {
17
17
 
18
18
  'tDepth': { value: null },
19
- 'tDiffuse': { value: null },
20
19
  'tNormal': { value: null },
21
20
  'size': { value: new Vector2(512, 512) },
22
21
 
@@ -25,7 +24,6 @@ const SAOShader = {
25
24
  'cameraProjectionMatrix': { value: new Matrix4() },
26
25
  'cameraInverseProjectionMatrix': { value: new Matrix4() },
27
26
 
28
- 'scale': { value: 1.0 },
29
27
  'intensity': { value: 0.1 },
30
28
  'bias': { value: 0.5 },
31
29
 
@@ -48,10 +46,6 @@ const SAOShader = {
48
46
 
49
47
  varying vec2 vUv;
50
48
 
51
- #if DIFFUSE_TEXTURE == 1
52
- uniform sampler2D tDiffuse;
53
- #endif
54
-
55
49
  uniform sampler2D tDepth;
56
50
 
57
51
  #if NORMAL_TEXTURE == 1
@@ -63,7 +57,6 @@ const SAOShader = {
63
57
  uniform mat4 cameraProjectionMatrix;
64
58
  uniform mat4 cameraInverseProjectionMatrix;
65
59
 
66
- uniform float scale;
67
60
  uniform float intensity;
68
61
  uniform float bias;
69
62
  uniform float kernelRadius;
@@ -74,14 +67,8 @@ const SAOShader = {
74
67
  // RGBA depth
75
68
 
76
69
  #include <packing>
77
-
78
- vec4 getDefaultColor( const in vec2 screenPosition ) {
79
- #if DIFFUSE_TEXTURE == 1
80
- return texture2D( tDiffuse, vUv );
81
- #else
82
- return vec4( 1.0 );
83
- #endif
84
- }
70
+
71
+ out float out_occlusion;
85
72
 
86
73
  float getDepth( const in vec2 screenPosition ) {
87
74
  #if DEPTH_PACKING == 1
@@ -114,16 +101,26 @@ const SAOShader = {
114
101
  return normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) );
115
102
  #endif
116
103
  }
117
-
118
- float scaleDividedByCameraFar;
104
+
105
+ // Non-sin based hash function, fast and has good randomness
106
+ float hash12(vec2 p){
107
+ vec3 p3 = fract(vec3(p.xyx) * .1031);
108
+ p3 += dot(p3, p3.yzx + 33.33);
109
+ return fract((p3.x + p3.y) * p3.z);
110
+ }
111
+
119
112
  float minResolutionMultipliedByCameraFar;
120
113
 
121
114
  float getOcclusion( const in vec3 centerViewPosition, const in vec3 centerViewNormal, const in vec3 sampleViewPosition ) {
122
115
  vec3 viewDelta = sampleViewPosition - centerViewPosition;
116
+
123
117
  float viewDistance = length( viewDelta );
124
- float scaledScreenDistance = scaleDividedByCameraFar * viewDistance;
125
-
126
- return max(0.0, (dot(centerViewNormal, viewDelta) - minResolutionMultipliedByCameraFar) / scaledScreenDistance - bias) / (1.0 + pow2( scaledScreenDistance ) );
118
+
119
+ float vn = dot( centerViewNormal, viewDelta );
120
+ float a2 = (vn - minResolutionMultipliedByCameraFar) / viewDistance - bias;
121
+ float a1 = (1.0 + pow2( viewDistance ) );
122
+
123
+ return max(0.0, a2) / a1;
127
124
  }
128
125
 
129
126
  // moving costly divides into consts
@@ -132,31 +129,34 @@ const SAOShader = {
132
129
 
133
130
  float getAmbientOcclusion( const in vec3 centerViewPosition ) {
134
131
  // precompute some variables require in getOcclusion.
135
- scaleDividedByCameraFar = scale / cameraFar;
136
132
  minResolutionMultipliedByCameraFar = minResolution * cameraFar;
137
133
  vec3 centerViewNormal = getViewNormal( centerViewPosition, vUv );
138
134
 
139
135
  // jsfiddle that shows sample pattern: https://jsfiddle.net/a16ff1p7/
140
- float angle = rand( vUv + randomSeed ) * PI2;
136
+ float angle = hash12( vUv + randomSeed ) * PI2;
141
137
  vec2 radius = vec2( kernelRadius * INV_NUM_SAMPLES ) / size;
142
- vec2 radiusStep = radius;
143
138
 
144
139
  float occlusionSum = 0.0;
145
140
  float weightSum = 0.0;
146
141
 
147
142
  for( int i = 0; i < NUM_SAMPLES; i ++ ) {
148
- vec2 sampleUv = vUv + vec2( cos( angle ), sin( angle ) ) * radius;
149
- radius += radiusStep;
143
+ vec2 sampleUv = vUv + vec2( cos( angle ), sin( angle ) ) * radius * float(i+1);
144
+
150
145
  angle += ANGLE_STEP;
151
146
 
152
147
  float sampleDepth = getDepth( sampleUv );
148
+
153
149
  if( sampleDepth >= ( 1.0 - EPSILON ) ) {
150
+ // skip, too far
154
151
  continue;
155
152
  }
156
153
 
157
154
  float sampleViewZ = getViewZ( sampleDepth );
155
+
158
156
  vec3 sampleViewPosition = getViewPosition( sampleUv, sampleDepth, sampleViewZ );
157
+
159
158
  occlusionSum += getOcclusion( centerViewPosition, centerViewNormal, sampleViewPosition );
159
+
160
160
  weightSum += 1.0;
161
161
  }
162
162
 
@@ -177,12 +177,8 @@ const SAOShader = {
177
177
  float ambientOcclusion = getAmbientOcclusion( viewPosition );
178
178
 
179
179
  float invOcclusion = 1.0 - ambientOcclusion;
180
- #if DIFFUSE_TEXTURE == 1
181
- gl_FragColor = getDefaultColor( vUv );
182
- gl_FragColor.xyz *= invOcclusion;
183
- #else
184
- gl_FragColor = vec4(invOcclusion,invOcclusion,invOcclusion,1.0);
185
- #endif
180
+
181
+ out_occlusion = invOcclusion;
186
182
  }`
187
183
 
188
184
  };
@@ -83,6 +83,7 @@ import { ShadedGeometrySystem } from "../../../ecs/mesh-v2/ShadedGeometrySystem.
83
83
  import { FogOfWar } from "../../../../ecs/fow/FogOfWar.js";
84
84
  import { FogOfWarRevealer } from "../../../../ecs/fow/FogOfWarRevealer.js";
85
85
  import Team from "../../../../../../model/game/ecs/component/Team.js";
86
+ import { AmbientOcclusionPostProcessEffect } from "../../buffer/simple-fx/ao/AmbientOcclusionPostProcessEffect.js";
86
87
 
87
88
  const geo_light_center_octahedron = new BoxBufferGeometry(0.1, 0.1, 0.1);
88
89
  const engineHarness = new EngineHarness();
@@ -163,6 +164,7 @@ function makeConfig(engine) {
163
164
 
164
165
  // Plugins
165
166
  config.addPlugin(ForwardPlusRenderingPlugin);
167
+ config.addPlugin(AmbientOcclusionPostProcessEffect);
166
168
  // config.addPlugin(MicronRenderPlugin);
167
169
 
168
170
  // Knowledge
@@ -11,13 +11,33 @@ function vertexShader() {
11
11
  `;
12
12
  }
13
13
 
14
- function fragmentShader() {
14
+ /**
15
+ *
16
+ * @param {string[]} [swizzle] what to read from the texture
17
+ * @returns {string}
18
+ */
19
+ function fragmentShader(swizzle = ['r', 'g', 'b', 'a']) {
20
+ const swizzle_read = [];
21
+ const n = swizzle.length;
22
+ for (let i = 0; i < n; i++) {
23
+ const token = swizzle[i];
24
+
25
+ if (typeof token === "string") {
26
+ swizzle_read[i] = `texel.${token}`;
27
+ } else if (typeof token === "number") {
28
+ swizzle_read[i] = Number(token).toFixed(4);
29
+ } else {
30
+ throw new Error(`Unsupported swizzle token '${token}'`);
31
+ }
32
+ }
33
+
15
34
  return `
16
35
  uniform sampler2D tTexture;
17
36
  varying vec2 vUv;
18
37
 
19
38
  void main(){
20
- gl_FragColor = texture2D( tTexture, vUv );
39
+ vec4 texel = texture2D( tTexture, vUv );
40
+ gl_FragColor = vec4(${swizzle_read.join(',')});
21
41
  }
22
42
  `;
23
43
  }
@@ -25,4 +45,4 @@ function fragmentShader() {
25
45
  export const ScreenSpaceQuadShader = {
26
46
  vertexShader,
27
47
  fragmentShader
28
- };
48
+ };
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.55",
8
+ "version": "2.33.59",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",