@woosh/meep-engine 2.43.3 → 2.43.5

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.
Files changed (52) hide show
  1. package/core/binary/BinaryBuffer.js +13 -1
  2. package/core/binary/BitSet.js +2 -2
  3. package/core/collection/array/array_range_equal_strict.js +22 -0
  4. package/core/collection/map/AsyncMapWrapper.js +13 -1
  5. package/core/collection/map/CachedAsyncMap.js +9 -2
  6. package/core/collection/map/CachedAsyncMap.spec.js +47 -0
  7. package/core/color/sRGB_to_linear.js +9 -4
  8. package/core/geom/3d/plane/orient3d_fast.js +3 -0
  9. package/core/geom/3d/plane/orient3d_robust.js +41 -0
  10. package/core/geom/3d/sphere/harmonics/README.md +15 -0
  11. package/core/geom/3d/sphere/harmonics/sh3_add.js +21 -0
  12. package/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +618 -0
  13. package/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +49 -0
  14. package/core/geom/3d/sphere/harmonics/sh3_sample_irradiance_by_direction.js +53 -0
  15. package/core/geom/3d/tetrahedra/TetrahedralMesh.js +251 -68
  16. package/core/geom/3d/tetrahedra/TetrahedralMesh.spec.js +80 -3
  17. package/core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js +75 -0
  18. package/core/geom/3d/tetrahedra/delaunay/Cavity.js +5 -1
  19. package/core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js +30 -31
  20. package/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +54 -18
  21. package/core/geom/3d/tetrahedra/delaunay/push_boundary_with_validation.js +27 -0
  22. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity.js +89 -0
  23. package/core/geom/3d/tetrahedra/delaunay/{tetrahedral_mesh_walk_toward_cavity.js → tetrahedral_mesh_walk_towards_containing_tetrahedron.js} +15 -12
  24. package/core/geom/3d/tetrahedra/delaunay/validate_cavity_boundary.js +60 -0
  25. package/core/geom/3d/tetrahedra/{point_in_tetrahedron_circumsphere.js → in_sphere_fast.js} +2 -4
  26. package/core/geom/3d/tetrahedra/in_sphere_robust.js +53 -0
  27. package/core/geom/3d/tetrahedra/prototypeTetrahedraBuilder.js +44 -35
  28. package/core/geom/3d/tetrahedra/validate_tetrahedral_mesh.js +85 -38
  29. package/core/geom/3d/util/make_justified_point_grid.js +31 -0
  30. package/core/process/delay.js +5 -0
  31. package/editor/Editor.js +3 -0
  32. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +195 -11
  33. package/editor/ecs/component/editors/ecs/ParameterTrackSetEditor.js +16 -0
  34. package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.js +4 -0
  35. package/engine/EngineHarness.js +11 -5
  36. package/engine/ecs/terrain/ecs/TerrainSystem.js +7 -1
  37. package/engine/ecs/transform/copy_three_transform.js +15 -0
  38. package/engine/graphics/ecs/light/Light.js +6 -1
  39. package/engine/graphics/ecs/light/LightSystem.d.ts +1 -1
  40. package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +2 -17
  41. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +2 -2
  42. package/engine/graphics/micron/plugin/shaded_geometry/MicronShadedGeometryRenderAdapter.js +9 -1
  43. package/engine/graphics/sh3/LightProbeVolume.js +595 -0
  44. package/engine/graphics/sh3/SH3VisualisationMaterial.js +79 -0
  45. package/engine/graphics/sh3/prototypeSH3Probe.js +427 -0
  46. package/engine/graphics/sh3/visualise_probe.js +40 -0
  47. package/engine/graphics/texture/atlas/TextureAtlas.js +15 -3
  48. package/engine/intelligence/blackboard/AbstractBlackboard.d.ts +1 -1
  49. package/package.json +2 -1
  50. package/samples/terrain/from_image_2.js +127 -82
  51. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_compute_cavity2.js +0 -224
  52. package/core/geom/3d/tetrahedra/delaunay/tetrahedral_mesh_insert_point.js +0 -98
@@ -0,0 +1,595 @@
1
+ import { TetrahedralMesh } from "../../../core/geom/3d/tetrahedra/TetrahedralMesh.js";
2
+ import {
3
+ compute_delaunay_tetrahedral_mesh
4
+ } from "../../../core/geom/3d/tetrahedra/delaunay/compute_delaunay_tetrahedral_mesh.js";
5
+ import { visualise_probe } from "./visualise_probe.js";
6
+ import {
7
+ build_tetrahedral_mesh_buffer_geometry
8
+ } from "../../../core/geom/3d/tetrahedra/build_tetrahedral_mesh_buffer_geometry.js";
9
+ import EntityBuilder from "../../ecs/EntityBuilder.js";
10
+ import { ShadedGeometry } from "../ecs/mesh-v2/ShadedGeometry.js";
11
+ import {
12
+ CubeCamera,
13
+ LinearEncoding,
14
+ LinearFilter,
15
+ LineBasicMaterial,
16
+ RGBAFormat,
17
+ Scene,
18
+ WebGLCubeRenderTarget
19
+ } from "three";
20
+ import { DrawMode } from "../ecs/mesh-v2/DrawMode.js";
21
+ import { Transform } from "../../ecs/transform/Transform.js";
22
+ import { array_copy } from "../../../core/collection/array/copyArray.js";
23
+ import { SurfacePoint3 } from "../../../core/geom/3d/SurfacePoint3.js";
24
+ import { Light } from "../ecs/light/Light.js";
25
+ import { threeMakeLight } from "../ecs/light/binding/three/threeMakeLight.js";
26
+ import { ThreeLightCache } from "../ecs/light/three/ThreeLightCache.js";
27
+ import { applyTransformToThreeObject } from "../ecs/mesh/applyTransformToThreeObject.js";
28
+ import { build_three_object } from "../ecs/mesh-v2/build_three_object.js";
29
+ import { ShadedGeometryFlags } from "../ecs/mesh-v2/ShadedGeometryFlags.js";
30
+ import { v3_length_sqr } from "../../../core/geom/v3_length_sqr.js";
31
+
32
+ const TEMP_CONTACT = new SurfacePoint3();
33
+
34
+ /**
35
+ * evaluate the basis functions
36
+ * shBasis is an Array[ 9 ]
37
+ *
38
+ * @param {number} x
39
+ * @param {number} y
40
+ * @param {number} z
41
+ * @param {number[]|ArrayLike<number>|Float32Array|Float64Array} shBasis
42
+ */
43
+ function getBasisAt(x, y, z, shBasis) {
44
+
45
+ // normal is assumed to be unit length
46
+
47
+
48
+ // band 0
49
+ shBasis[0] = 0.282095;
50
+
51
+ // band 1
52
+ shBasis[1] = 0.488603 * y;
53
+ shBasis[2] = 0.488603 * z;
54
+ shBasis[3] = 0.488603 * x;
55
+
56
+ // band 2
57
+ shBasis[4] = 1.092548 * x * y;
58
+ shBasis[5] = 1.092548 * y * z;
59
+ shBasis[6] = 0.315392 * (3 * z * z - 1);
60
+ shBasis[7] = 1.092548 * x * z;
61
+ shBasis[8] = 0.546274 * (x * x - y * y);
62
+
63
+ }
64
+
65
+ /**
66
+ *
67
+ * @param {Uint8Array} data
68
+ * @param {THREE.WebGLRenderer} renderer
69
+ * @param {THREE.WebGLCubeRenderTarget} cubeRenderTarget
70
+ * @return {Float64Array}
71
+ */
72
+ function fromCubeRenderTarget(data, renderer, cubeRenderTarget) {
73
+
74
+ // The renderTarget must be set to RGBA in order to make readRenderTargetPixels works
75
+ let total_weight = 0;
76
+
77
+ const __shared_buffer = new ArrayBuffer((27 + 9) * 8);
78
+
79
+ const sh_basis = new Float64Array(__shared_buffer, 0, 9);
80
+ const coefficients = new Float64Array(__shared_buffer, 9 * 8, 27);
81
+
82
+ const image_size = cubeRenderTarget.width;
83
+
84
+ const pixel_size = 2 / image_size;
85
+
86
+ let normal_x = 0, normal_y = 0, normal_z = 0;
87
+
88
+ for (let faceIndex = 0; faceIndex < 6; faceIndex++) {
89
+
90
+ renderer.readRenderTargetPixels(cubeRenderTarget, 0, 0, image_size, image_size, data, faceIndex);
91
+
92
+ // RGBA assumed
93
+
94
+ for (let i = 0, il = data.length; i < il; i += 4) {
95
+
96
+
97
+ // pixel coordinate on unit cube
98
+
99
+ const pixelIndex = i / 4;
100
+
101
+ const col = -1 + (pixelIndex % image_size + 0.5) * pixel_size;
102
+
103
+ const row = 1 - (Math.floor(pixelIndex / image_size) + 0.5) * pixel_size;
104
+
105
+
106
+ switch (faceIndex) {
107
+
108
+ case 0:
109
+ normal_x = 1;
110
+ normal_y = row;
111
+ normal_z = -col;
112
+ break;
113
+
114
+ case 1:
115
+ normal_x = -1;
116
+ normal_y = row;
117
+ normal_z = col;
118
+ break;
119
+
120
+ case 2:
121
+ normal_x = col;
122
+ normal_y = 1;
123
+ normal_z = -row;
124
+ break;
125
+
126
+ case 3:
127
+ normal_x = col;
128
+ normal_y = -1;
129
+ normal_z = row;
130
+ break;
131
+
132
+ case 4:
133
+ normal_x = col;
134
+ normal_y = row;
135
+ normal_z = 1;
136
+ break;
137
+
138
+ case 5:
139
+ normal_x = -col;
140
+ normal_y = row;
141
+ normal_z = -1;
142
+ break;
143
+
144
+ }
145
+
146
+ // weight assigned to this pixel
147
+
148
+ const length_squared = v3_length_sqr(normal_x, normal_y, normal_z);
149
+
150
+ const length = Math.sqrt(length_squared);
151
+
152
+ const weight = 4 / (length * length_squared);
153
+
154
+ total_weight += weight;
155
+
156
+ // direction vector to this pixel
157
+ normal_x /= length;
158
+ normal_y /= length;
159
+ normal_z /= length;
160
+
161
+ // evaluate SH basis functions in direction dir
162
+ getBasisAt(normal_x, normal_y, normal_z, sh_basis);
163
+
164
+ // pixel color, already in linear space so no sRGB->Linear conversion necessary
165
+
166
+ // 0.003921 constant value is 1/255, a conversion value from UINT8 to normalized float
167
+ const weight_conv = weight * 0.00392156862745098;
168
+
169
+ const color_r = data[i] * weight_conv;
170
+ const color_g = data[i + 1] * weight_conv;
171
+ const color_b = data[i + 2] * weight_conv;
172
+
173
+ // accumulate
174
+ for (let j = 0; j < 27; j += 3) {
175
+
176
+ coefficients[j] += sh_basis[j] * color_r;
177
+ coefficients[j + 1] += sh_basis[j] * color_g;
178
+ coefficients[j + 2] += sh_basis[j] * color_b;
179
+
180
+ }
181
+
182
+ }
183
+
184
+ }
185
+
186
+ // normalize
187
+ const norm = (4 * Math.PI) / total_weight;
188
+
189
+ for (let j = 0; j < 27; j++) {
190
+
191
+ coefficients[j] *= norm;
192
+
193
+ }
194
+
195
+ return coefficients;
196
+
197
+ }
198
+
199
+
200
+ class CubeRenderer {
201
+ constructor() {
202
+ /**
203
+ *
204
+ * @type {GraphicsEngine|null}
205
+ */
206
+ this.ge = null;
207
+
208
+ /**
209
+ *
210
+ * @type {THREE.WebGLRenderer|null}
211
+ * @private
212
+ */
213
+ this.__renderer = null;
214
+
215
+ this.render_target = new WebGLCubeRenderTarget(32, {
216
+ format: RGBAFormat,
217
+ minFilter: LinearFilter,
218
+ magFilter: LinearFilter,
219
+ depthBuffer: true,
220
+ scissorTest: false,
221
+ stencilBuffer: false,
222
+ generateMipmaps: false,
223
+ encoding: LinearEncoding
224
+ });
225
+
226
+ /**
227
+ * Used to read out GPU memory for each face
228
+ * @type {Uint8Array}
229
+ * @private
230
+ */
231
+ this.__cube_face_raw = new Uint8Array(this.render_target.width * this.render_target.width * 4);
232
+
233
+
234
+ this.__cube_camera = new CubeCamera(0.1, 100, this.render_target);
235
+
236
+ /**
237
+ *
238
+ * @type {Scene}
239
+ * @private
240
+ */
241
+ this.__scene = new Scene();
242
+ }
243
+
244
+ /**
245
+ *
246
+ * @param {EntityComponentDataset} ecd
247
+ */
248
+ build_scene(ecd) {
249
+ this.__scene.children.splice(0, this.__scene.children.length);
250
+
251
+ ecd.traverseEntities([ShadedGeometry, Transform], (sg, t, entity) => {
252
+ const object3D = build_three_object(sg);
253
+
254
+ object3D.material = sg.material.clone();
255
+
256
+ applyTransformToThreeObject(object3D, t);
257
+
258
+ array_copy(t.matrix, 0, object3D.matrixWorld.elements, 0, 16);
259
+
260
+ this.__scene.add(object3D);
261
+ });
262
+
263
+ const lightCache = new ThreeLightCache();
264
+
265
+ ecd.traverseEntities([Light, Transform], (light, t) => {
266
+
267
+ const object = threeMakeLight(light, lightCache);
268
+ applyTransformToThreeObject(object, t);
269
+
270
+ this.__scene.add(object);
271
+ });
272
+
273
+ // this.__scene.add(this.__cube_camera);
274
+ }
275
+
276
+ bake_start() {
277
+ // this.__renderer = WebGLRendererPool.global.get();
278
+
279
+ this.__renderer = this.ge.getRenderer();
280
+ }
281
+
282
+ bake_end() {
283
+ // WebGLRendererPool.global.release(this.__renderer);
284
+
285
+ this.__renderer = null;
286
+ }
287
+
288
+ /**
289
+ *
290
+ * @param {number[]} position
291
+ * @param {number} position_offset
292
+ * @param {number[]} output
293
+ * @param {number} output_offset
294
+ */
295
+ bake(position, position_offset, output, output_offset) {
296
+ // console.warn(position_offset, output_offset);
297
+
298
+ // const ge = this.ge;
299
+ const renderer = this.__renderer;
300
+
301
+ const _rt = renderer.getRenderTarget();
302
+ const _rt_active_face = renderer.getActiveCubeFace();
303
+ const _acc = renderer.autoClearColor;
304
+ const _acd = renderer.autoClearDepth;
305
+ const _ac = renderer.autoClear;
306
+ const _outputEncoding = renderer.outputEncoding;
307
+
308
+ renderer.outputEncoding = LinearEncoding;
309
+
310
+ renderer.autoClearColor = true;
311
+ renderer.autoClearDepth = true;
312
+ renderer.autoClear = true;
313
+
314
+ this.__cube_camera.position.fromArray(position, position_offset);
315
+ this.__cube_camera.update(renderer, this.__scene);
316
+
317
+
318
+ // compute coefficients from renderer
319
+ const probe_coefficients = fromCubeRenderTarget(this.__cube_face_raw, renderer, this.render_target);
320
+
321
+ array_copy(probe_coefficients, 0, output, output_offset, 27);
322
+
323
+ // restore state
324
+ renderer.setRenderTarget(_rt, _rt_active_face);
325
+ renderer.autoClearColor = _acc;
326
+ renderer.autoClearDepth = _acd;
327
+ renderer.autoClear = _ac;
328
+ renderer.outputEncoding = _outputEncoding;
329
+ }
330
+ }
331
+
332
+ class Baker {
333
+ constructor() {
334
+ /**
335
+ *
336
+ * @type {EntityComponentDataset|null}
337
+ * @private
338
+ */
339
+ this.__ecd = null;
340
+
341
+ /**
342
+ *
343
+ * @type {ExplicitBinaryBoundingVolumeHierarchy|null}
344
+ * @private
345
+ */
346
+ this.__bvh = null;
347
+
348
+ this.__min_bounce_count = 3;
349
+ this.__max_bounce_count = 100;
350
+
351
+ this._ren = new CubeRenderer();
352
+ }
353
+
354
+ /**
355
+ * @param {ExplicitBinaryBoundingVolumeHierarchy} bvh
356
+ */
357
+ set_bvh(bvh) {
358
+ this.__bvh = bvh;
359
+ }
360
+
361
+ build_spatial() {
362
+
363
+ }
364
+
365
+ /**
366
+ *
367
+ * @param {number[]} irradiance RGB irradiance result is written here
368
+ * @param {number} bounce_count number of reflections before this trace
369
+ * @param {number} origin_x
370
+ * @param {number} origin_y
371
+ * @param {number} origin_z
372
+ * @param {number} direction_x
373
+ * @param {number} direction_y
374
+ * @param {number} direction_z
375
+ */
376
+ trace_ray(irradiance, bounce_count,
377
+ origin_x, origin_y, origin_z,
378
+ direction_x, direction_y, direction_z) {
379
+
380
+ //
381
+
382
+ if (!this.ray_cast(TEMP_CONTACT, origin_x, origin_y, origin_z, direction_x, direction_y, direction_z)) {
383
+ // no hit, assume irradiance is zeroed - black
384
+
385
+ return;
386
+ }
387
+
388
+ // record irradiance
389
+
390
+ // reflect ray around normal
391
+ // TODO apply BDRF to compute reflected ray
392
+
393
+ if (bounce_count > this.__max_bounce_count) {
394
+ // terminate
395
+ }
396
+
397
+ }
398
+
399
+ /**
400
+ *
401
+ * @param contact
402
+ * @param origin_x
403
+ * @param origin_y
404
+ * @param origin_z
405
+ * @param direction_x
406
+ * @param direction_y
407
+ * @param direction_z
408
+ * @returns {boolean}
409
+ */
410
+ ray_cast(contact,
411
+ origin_x, origin_y, origin_z,
412
+ direction_x, direction_y, direction_z
413
+ ) {
414
+
415
+ }
416
+
417
+ prepare_scene() {
418
+
419
+ }
420
+
421
+ /**
422
+ *
423
+ * @param {number[]} position
424
+ * @param {number} position_offset
425
+ * @param {number[]} output
426
+ * @param {number} output_offset
427
+ */
428
+ bake_sh3_cube(
429
+ position, position_offset,
430
+ output, output_offset
431
+ ) {
432
+
433
+ this._ren.bake(position, position_offset, output, output_offset);
434
+
435
+ }
436
+
437
+ /**
438
+ *
439
+ * @param {Engine} engine
440
+ */
441
+ prepare(engine) {
442
+ this._ren.ge = engine.graphics;
443
+
444
+ this._ren.build_scene(engine.entityManager.dataset);
445
+
446
+ this._ren.bake_start();
447
+ }
448
+
449
+ finish() {
450
+ this._ren.bake_end();
451
+ }
452
+ }
453
+
454
+ export class LightProbeVolume {
455
+ constructor() {
456
+ this.__positions = [];
457
+
458
+ this.__mesh = new TetrahedralMesh();
459
+
460
+ this.__probe_data = [];
461
+
462
+ this.__length = 0;
463
+ }
464
+
465
+ /**
466
+ *
467
+ * @param {number} x
468
+ * @param {number} y
469
+ * @param {number} z
470
+ */
471
+ add_point(x, y, z) {
472
+ const i = this.__length;
473
+
474
+ this.__positions[i * 3] = x;
475
+ this.__positions[i * 3 + 1] = y;
476
+ this.__positions[i * 3 + 2] = z;
477
+
478
+ for (let j = 0; j < 9 * 3; j++) {
479
+ this.__probe_data[i * 9 * 3 + j] = 1; // fill with white
480
+ }
481
+
482
+ this.__length++;
483
+ }
484
+
485
+ /**
486
+ *
487
+ * @param {number} index
488
+ * @param {number[]} source
489
+ * @param {number} source_offset
490
+ */
491
+ white_probe(index, source, source_offset) {
492
+ array_copy(source, source_offset, this.__probe_data, index * 9 * 3, 9 * 3);
493
+ }
494
+
495
+ /**
496
+ * Build tetrahedral mesh
497
+ */
498
+ build_mesh() {
499
+
500
+ const t0 = performance.now();
501
+ compute_delaunay_tetrahedral_mesh(this.__mesh, this.__positions, this.__length);
502
+
503
+ const t1 = performance.now();
504
+
505
+ // optional step to improve memory utilization
506
+ this.__mesh.compact();
507
+
508
+ const t2 = performance.now();
509
+
510
+ console.log(`Tetrahedral mesh (${this.__length} points, ${this.__mesh.size()} tets) build took ${t2 - t0}ms`);
511
+ }
512
+
513
+
514
+ /**
515
+ * Bake light probes
516
+ * @param {Engine} engine
517
+ */
518
+ bake(engine) {
519
+ const baker = new Baker();
520
+ // baker.set_bvh(entityManager.getSystem(ShadedGeometrySystem).__bvh_binary);
521
+
522
+ baker.prepare(engine);
523
+
524
+ const t0 = performance.now();
525
+
526
+
527
+ for (let i = 0; i < this.__length; i++) {
528
+
529
+ baker.bake_sh3_cube(
530
+ this.__positions, i * 3,
531
+ this.__probe_data, i * 9 * 3
532
+ );
533
+
534
+ }
535
+
536
+ const t1 = performance.now();
537
+
538
+ console.log(`Baked ${this.__length} probes in ${(t1 - t0)}ms, ~${((t1 - t0) / this.__length).toFixed(2)}ms per probe`);
539
+
540
+ baker.finish();
541
+ }
542
+
543
+ /**
544
+ *
545
+ * @param {EntityComponentDataset} ecd
546
+ * @param {number} size
547
+ * @param {boolean} shadow
548
+ */
549
+ visualize_probes({ ecd, size = 0.2, shadow = false }) {
550
+ for (let i = 0; i < this.__length; i++) {
551
+ visualise_probe({
552
+ size,
553
+ position: this.__positions,
554
+ position_offset: i * 3,
555
+ sh: this.__probe_data,
556
+ sh_offset: i * 3 * 9,
557
+ shadow: shadow
558
+ }).build(ecd);
559
+ }
560
+ }
561
+
562
+ /**
563
+ *
564
+ * @param {EntityComponentDataset} ecd
565
+ * @param {string|number} color CSS color definition
566
+ */
567
+ visualize_mesh({ ecd, color = '#FF00FF' }) {
568
+
569
+ const geometry = build_tetrahedral_mesh_buffer_geometry(this.__mesh, this.__positions);
570
+
571
+ const sg = ShadedGeometry.from(geometry, new LineBasicMaterial({
572
+ color: color,
573
+ transparent: true,
574
+ opacity: 0.35,
575
+ depthWrite: false
576
+ }), DrawMode.LineSegments);
577
+
578
+ sg.clearFlag(ShadedGeometryFlags.CastShadow);
579
+ sg.clearFlag(ShadedGeometryFlags.ReceiveShadow);
580
+
581
+ new EntityBuilder()
582
+ .add(sg)
583
+ .add(new Transform())
584
+ .build(ecd);
585
+ }
586
+
587
+ /**
588
+ *
589
+ * @param {Engine} engine
590
+ */
591
+ build(engine) {
592
+ this.build_mesh();
593
+ this.bake(engine);
594
+ }
595
+ }
@@ -0,0 +1,79 @@
1
+ import { ShaderMaterial, Vector3 } from "three";
2
+
3
+ const shader_vx = `
4
+ varying vec3 vNormal;
5
+
6
+ void main() {
7
+
8
+ vNormal = normalize( normalMatrix * normal );
9
+
10
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
11
+
12
+ }
13
+ `;
14
+
15
+ const shader_fg = `
16
+ #define RECIPROCAL_PI 0.318309886
17
+
18
+ vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {
19
+ // matrix is assumed to be orthogonal
20
+ return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );
21
+ }
22
+
23
+ // source: https://graphics.stanford.edu/papers/envmap/envmap.pdf
24
+ vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {
25
+ // normal is assumed to have unit length
26
+ float x = normal.x, y = normal.y, z = normal.z;
27
+ // band 0
28
+ vec3 result = shCoefficients[ 0 ] * 0.886227;
29
+ // band 1
30
+ result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;
31
+ result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;
32
+ result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;
33
+ // band 2
34
+ result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;
35
+ result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;
36
+ result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );
37
+ result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;
38
+ result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );
39
+
40
+ return result;
41
+ }
42
+
43
+ uniform vec3 sh[ 9 ]; // sh coefficients
44
+ uniform float intensity; // light probe intensity
45
+ varying vec3 vNormal;
46
+
47
+ void main() {
48
+ vec3 normal = normalize( vNormal );
49
+ vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );
50
+ vec3 irradiance = shGetIrradianceAt( worldNormal, sh );
51
+ vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity;
52
+ gl_FragColor = linearToOutputTexel( vec4( outgoingLight, 1.0 ) );
53
+ }
54
+ `;
55
+
56
+ export class SH3VisualisationMaterial extends ShaderMaterial {
57
+
58
+ constructor() {
59
+ const sh = [];
60
+
61
+ for (let i = 0; i < 9; i++) {
62
+ sh.push(new Vector3());
63
+ }
64
+
65
+ super({
66
+ fragmentShader: shader_fg,
67
+ vertexShader: shader_vx,
68
+ uniforms: {
69
+ intensity: {
70
+ value: 1,
71
+ },
72
+ sh: {
73
+ value: sh
74
+ }
75
+ }
76
+ })
77
+ }
78
+
79
+ }