@woosh/meep-engine 2.43.20 → 2.43.21

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.
@@ -16,12 +16,21 @@ export class SGMeshHighlightSystem extends System {
16
16
  * @param {number} entity
17
17
  * @private
18
18
  */
19
- __apply_highlight( event, entity) {
19
+ __apply_highlight(event, entity) {
20
20
  const ecd = this.entityManager.dataset;
21
21
 
22
22
  const sg_mesh = ecd.getComponent(entity, SGMesh);
23
+
24
+ if (sg_mesh === undefined) {
25
+ return;
26
+ }
27
+
23
28
  const highlight = ecd.getComponent(entity, Highlight);
24
29
 
30
+ if (highlight === undefined) {
31
+ return;
32
+ }
33
+
25
34
  const node = sg_mesh.__node;
26
35
 
27
36
  node.traverse(t => {
@@ -35,7 +44,7 @@ export class SGMeshHighlightSystem extends System {
35
44
  * @param {number} entity
36
45
  * @private
37
46
  */
38
- __remove_highlight( event, entity) {
47
+ __remove_highlight(event, entity) {
39
48
 
40
49
  const ecd = this.entityManager.dataset;
41
50
 
@@ -56,7 +65,7 @@ export class SGMeshHighlightSystem extends System {
56
65
  */
57
66
  link(highlight, mesh, entity) {
58
67
  if (mesh.__node !== null) {
59
- this.__apply_highlight(undefined, entity);
68
+ this.__apply_highlight(undefined, entity);
60
69
  }
61
70
 
62
71
  const ecd = this.entityManager.dataset;
@@ -1,3 +1,3 @@
1
1
  export class MicronGeometryPatch {
2
-
2
+ allocate_metadata(buffer: ArrayBuffer, offset: number): number
3
3
  }
@@ -35,7 +35,7 @@ export class MicronGeometryPatch {
35
35
  this.group_bounding_box = null;
36
36
 
37
37
  /**
38
- *
38
+ * TODO get rid of this, use bounding box instead for smaller memory footprint
39
39
  * @type {number[]|Float32Array}
40
40
  */
41
41
  this.group_bounding_box_corners = null;
@@ -124,6 +124,7 @@ export class MicronGeometryPatch {
124
124
  * @returns {number}
125
125
  */
126
126
  allocate_metadata(buffer, offset) {
127
+ assert.isNonNegativeInteger(offset, 'offset');
127
128
  assert.greaterThanOrEqual(buffer.byteLength, offset + 144, 'buffer underflow');
128
129
 
129
130
  this.bounding_box = new Float32Array(buffer, offset, 6);
@@ -54,6 +54,10 @@ export class PathTracedMesh {
54
54
  this.__local_scale_inverse = Math.hypot(scale_x, scale_y, scale_z);
55
55
  }
56
56
 
57
+ get transform() {
58
+ return this.__transform;
59
+ }
60
+
57
61
  /**
58
62
  *
59
63
  * @param {number[]} out
@@ -23,14 +23,12 @@ import { linear_to_sRGB } from "../../../../core/color/linear_to_sRGB.js";
23
23
  import { random_in_hemisphere } from "./random_in_hemisphere.js";
24
24
  import { sample_triangle_attribute } from "./sample_triangle_attribute.js";
25
25
  import { vec3_uint8_to_float } from "./vec3_uint8_to_float.js";
26
- import { make_sky_rtiw } from "./make_sky_rtiw.js";
27
26
  import { apply_texture_clamping_to_coordinate } from "./apply_texture_clamping_to_coordinate.js";
28
27
  import { ebvh_sort_for_traversal_depth_first } from "../../../../core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js";
29
- import { clamp01 } from "../../../../core/math/clamp01.js";
30
28
  import { v3_dot } from "../../../../core/geom/v3_dot.js";
31
- import { LightType } from "../../ecs/light/LightType.js";
32
- import { make_zero_vector3 } from "./make_zero_vector3.js";
33
29
  import { make_one_vector3 } from "./make_one_vector3.js";
30
+ import { transform_normal_m4 } from "./ray_hit_apply_transform.js";
31
+ import { ray3_array_compose } from "../../../../core/geom/3d/ray/ray3_array_compose.js";
34
32
 
35
33
  /*
36
34
  Ray hit data layout:
@@ -49,13 +47,22 @@ const texture_uv = [0, 0];
49
47
  const color = [];
50
48
 
51
49
  const irradiance = [0, 0, 0];
50
+
51
+ /**
52
+ *
53
+ * @type {number[]|vec3}
54
+ */
55
+ const throughputColor = [1, 1, 1];
52
56
  const trace_result = [];
53
57
 
54
- const _ray = [];
58
+ const _ray_0 = [];
59
+ const _ray_1 = [];
55
60
 
56
61
  const tmp_0 = [];
57
62
  const tmp_1 = [];
58
63
 
64
+ const null_output = [];
65
+
59
66
 
60
67
  export class PathTracer {
61
68
  constructor() {
@@ -320,6 +327,9 @@ export class PathTracer {
320
327
  const normal_array = normal_attribute.array;
321
328
 
322
329
  sample_triangle_attribute(out, 3, index_0, index_1, index_2, normal_array, 3, u, v);
330
+
331
+ // apply transform
332
+ transform_normal_m4(out, 3, out, 3, mesh.transform);
323
333
  } else {
324
334
  // copy hit normal
325
335
  array_copy(hit, 3, out, 3, 3);
@@ -417,16 +427,17 @@ export class PathTracer {
417
427
  *
418
428
  * @param {number[]} out
419
429
  * @param {number} out_offset
420
- * @param {number[]} hit_info
421
- * @param {number} hit_info_address
430
+ * @param {number[]} ray
431
+ * @param {number} ray_address
422
432
  */
423
- sample_lights(out, out_offset, hit_info, hit_info_address) {
433
+ sample_lights(out, out_offset, ray, ray_address) {
424
434
  let lights_sampled = false;
425
435
 
426
436
  const lights = this.__lights;
427
437
  const light_count = lights.length;
428
438
 
429
439
  for (let i = 0; i < 3; i++) {
440
+ // initialize contribution to 0
430
441
  out[out_offset + i] = 0;
431
442
  }
432
443
 
@@ -436,25 +447,47 @@ export class PathTracer {
436
447
  if (light.isDirectionalLight === true) {
437
448
  const dir = light.direction;
438
449
 
450
+ // see https://github.com/mrdoob/three.js/blob/f0a9e0cf90a2f1ba5017fcb7fd46f02748b920cf/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js#L172
451
+
452
+ const ray_direction_x = ray[ray_address + 3];
453
+ const ray_direction_y = ray[ray_address + 4];
454
+ const ray_direction_z = ray[ray_address + 5];
455
+
456
+ const light_dir_inv_x = -dir.x;
457
+ const light_dir_inv_y = -dir.y;
458
+ const light_dir_inv_z = -dir.z;
459
+
460
+ const dotNL = v3_dot(ray_direction_x, ray_direction_y, ray_direction_z, light_dir_inv_x, light_dir_inv_y, light_dir_inv_z);
461
+
462
+ if (dotNL <= 0) {
463
+ // no contribution, facing away from the light
464
+ continue;
465
+ }
466
+
467
+ const ray_origin_x = ray[ray_address + 0];
468
+ const ray_origin_y = ray[ray_address + 1];
469
+ const ray_origin_z = ray[ray_address + 2];
470
+
471
+ ray3_array_compose(_ray_1,
472
+ ray_origin_x, ray_origin_y, ray_origin_z,
473
+ light_dir_inv_x, light_dir_inv_y, light_dir_inv_z
474
+ );
475
+
439
476
  // check if there are any obstacles in the direction of the light
440
- if (this.trace([], [
441
- hit_info[hit_info_address + 0],
442
- hit_info[hit_info_address + 1],
443
- hit_info[hit_info_address + 2],
444
- -dir.x, -dir.y, -dir.z
445
- ], 0.0001, Infinity) > 0) {
477
+ if (this.trace(null_output, _ray_1, 0.0001, Infinity) > 0) {
446
478
  // light is occluded
447
- break;
479
+ continue;
448
480
  }
449
481
 
450
- // see https://github.com/mrdoob/three.js/blob/f0a9e0cf90a2f1ba5017fcb7fd46f02748b920cf/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js#L172
451
482
 
452
- const dotNL = clamp01(v3_dot(hit_info[hit_info_address + 3], hit_info[hit_info_address + 4], hit_info[hit_info_address + 5], -dir.x, -dir.y, -dir.z));
483
+ const intensity = dotNL * light.intensity.getValue();
453
484
 
454
485
  for (let j = 0; j < 3; j++) {
455
486
  // irradiance
456
- out[out_offset + j] += dotNL * light.color[j] * light.intensity.getValue();
487
+ out[out_offset + j] += light.color[j] * intensity;
457
488
  }
489
+
490
+ lights_sampled = true;
458
491
  }
459
492
  }
460
493
 
@@ -464,28 +497,30 @@ export class PathTracer {
464
497
  path_trace(out, ray, max_distance, max_bounce, random = Math.random) {
465
498
  //TODO add importance sampling, see https://raytracing.github.io/books/RayTracingTheRestOfYourLife.html#lightscattering/thescatteringpdf
466
499
 
467
- array_copy(ray, 0, _ray, 0, 6);
500
+ array_copy(ray, 0, _ray_0, 0, 6);
468
501
 
469
- irradiance[0] = 0;
470
- irradiance[1] = 0;
471
- irradiance[2] = 0;
502
+ irradiance[0] = 1;
503
+ irradiance[1] = 1;
504
+ irradiance[2] = 1;
472
505
 
473
506
  let got_emission = false;
474
507
 
475
508
  let i;
476
509
  for (i = 0; i < max_bounce; i++) {
477
510
 
478
- const hit_distance = this.trace(trace_result, _ray, 0.0001, max_distance);
511
+ const hit_distance = this.trace(trace_result, _ray_0, 0.0001, max_distance);
512
+
479
513
 
480
514
  if (hit_distance < 0) {
481
515
  // ray didn't hit anything
482
516
 
483
517
  // sample "environment" and terminate path as there is nothing to reflect off of
484
518
 
485
- this.sample_background(tmp_0, 0, _ray, 3);
519
+ this.sample_background(tmp_0, 0, _ray_0, 3);
486
520
 
487
521
  vec3.multiply(irradiance, irradiance, tmp_0);
488
522
 
523
+
489
524
  got_emission = true;
490
525
 
491
526
  break;
@@ -498,28 +533,36 @@ export class PathTracer {
498
533
  this.sample_material(tmp_0, trace_result);
499
534
 
500
535
  // adjust normal on the hit
501
- // array_copy(tmp_0, 3, trace_result, 3, 3);
536
+ array_copy(tmp_0, 3, trace_result, 3, 3);
502
537
 
503
- // sample light
504
- this.sample_lights(tmp_1, 0, trace_result, 0);
505
- vec3.add(irradiance, irradiance, tmp_1);
506
538
 
507
539
  // reflect ray
508
- array_copy(trace_result, 0, _ray, 0, 3);
540
+ array_copy(trace_result, 0, _ray_0, 0, 3);
509
541
 
510
542
  // getBiasedNormalSample(_ray, 3, tmp, 3, 1, random);
511
- random_in_hemisphere(random, _ray, 3, tmp_0, 3);
543
+ random_in_hemisphere(random, _ray_0, 3, tmp_0, 3);
512
544
 
545
+ // accumulate
513
546
  vec3.multiply(irradiance, irradiance, tmp_0);
514
547
  }
515
548
 
516
- if (got_emission === false) {
517
- // no light source was hit, propagate shadow(darkness) along the ray
518
- irradiance[0] = 0;
519
- irradiance[1] = 0;
520
- irradiance[2] = 0;
549
+
550
+ if (i > 0) {
551
+
552
+ // directly sample light at the end of the path
553
+ this.sample_lights(tmp_1, 0, trace_result, 0);
554
+
555
+ vec3.multiply(irradiance, irradiance, tmp_1);
521
556
  }
522
557
 
558
+ //
559
+ // if (got_emission === false) {
560
+ // // no light source was hit, propagate shadow(darkness) along the ray
561
+ // irradiance[0] = 0;
562
+ // irradiance[1] = 0;
563
+ // irradiance[2] = 0;
564
+ // }
565
+
523
566
  array_copy(irradiance, 0, out, 0, 3);
524
567
  }
525
568
  }
@@ -534,3 +577,13 @@ function compute_hit_reaction_standard_material(material, hit) {
534
577
  }
535
578
 
536
579
 
580
+ /**
581
+ *
582
+ * @param {number[]|vec3} out
583
+ * @param {number} value
584
+ */
585
+ function vec3_set_scalar(out, value) {
586
+ out[0] = value;
587
+ out[1] = value;
588
+ out[2] = value;
589
+ }
@@ -21,7 +21,7 @@ import { Camera } from "../../ecs/camera/Camera.js";
21
21
  import Vector3 from "../../../../core/geom/Vector3.js";
22
22
  import { scaleSampler2D } from "../../texture/sampler/scaleSampler2D.js";
23
23
  import { seededRandom } from "../../../../core/math/random/seededRandom.js";
24
- import { mat4 } from "gl-matrix";
24
+ import { mat4, vec3 } from "gl-matrix";
25
25
  import { v3_distance } from "../../../../core/geom/v3_distance.js";
26
26
  import ConcurrentExecutor from "../../../../core/process/executor/ConcurrentExecutor.js";
27
27
  import Task from "../../../../core/process/task/Task.js";
@@ -38,6 +38,7 @@ import { Color } from "../../../../core/color/Color.js";
38
38
  import { min2 } from "../../../../core/math/min2.js";
39
39
  import { makeGeometryIndexed } from "../../geometry/buffered/makeGeometryIndexed.js";
40
40
  import { DirectionalLight } from "../../render/forward_plus/model/DirectionalLight.js";
41
+ import { kelvin_to_rgb } from "../../../../core/color/kelvin_to_rgb.js";
41
42
 
42
43
  document.body.style.margin = 0;
43
44
  document.body.style.overflow = "hidden";
@@ -103,6 +104,32 @@ const gltfLoader = new GLTFLoader();
103
104
  // );
104
105
  //
105
106
 
107
+ function make_sun({
108
+ temperature = 5000, // around clear-sky day
109
+ color = '#FFFFFF',
110
+ intensity = 1,
111
+ direction = new Vector3(-0.5, -1, -0.05)
112
+ } = {}) {
113
+
114
+ const temp_c = new Color();
115
+
116
+ kelvin_to_rgb(temp_c, 0, temperature);
117
+
118
+ const light = new DirectionalLight();
119
+
120
+ light.color.parse(color);
121
+ vec3.multiply(light.color, light.color, temp_c);
122
+
123
+ const n = vec3.length(light.color);
124
+ vec3.scale(light.color, light.color, 1 / n); // normalize color vector
125
+
126
+ light.intensity.set(intensity * n);
127
+ light.direction.copy(direction);
128
+ light.direction.normalize();
129
+
130
+
131
+ return light;
132
+ }
106
133
 
107
134
  function prepare_scene_sphere_01(pt, camera) {
108
135
  camera.position.set(0, 0, -2.1);
@@ -132,7 +159,7 @@ function prepare_scene_sphere_01(pt, camera) {
132
159
  light.direction.set(-1, -1, 0);
133
160
  light.direction.normalize();
134
161
 
135
- pt.addLight(light);
162
+ pt.addLight(make_sun());
136
163
  }
137
164
 
138
165
  function promise_ply(url) {
@@ -189,6 +216,8 @@ async function prepare_scene_gltf(pt, camera, url) {
189
216
 
190
217
  box3.getBoundingSphere(sphere);
191
218
 
219
+ pt.addLight(make_sun());
220
+
192
221
  camera.position.set(1, 1.3, 1)
193
222
  .normalize()
194
223
  .multiplyScalar(sphere.radius * 2.6);
@@ -226,6 +255,8 @@ async function prepare_scene_lucy(pt, camera) {
226
255
  }).matrix);
227
256
 
228
257
 
258
+ pt.addLight(make_sun());
259
+
229
260
  camera.position.set(0, 0, -2300);
230
261
  camera.lookAt(0, 0, 0);
231
262
  }
@@ -336,6 +367,8 @@ async function prepare_scene_rtiow(pt, camera) {
336
367
  }
337
368
 
338
369
 
370
+ pt.addLight(make_sun());
371
+
339
372
  camera.position.set(13, 2, 3);
340
373
  camera.lookAt(0, 0, 0);
341
374
 
@@ -376,7 +409,7 @@ function* render(target, pt, camera, progress = { current: 0, total: 0 }) {
376
409
  const pixel_scale_x = 1 / (width - 1);
377
410
  const pixel_scale_y = 1 / (height - 1);
378
411
 
379
- const pixel_sample_count = 64;
412
+ const pixel_sample_count = 500;
380
413
 
381
414
  progress.total = width * height;
382
415
 
@@ -426,7 +459,7 @@ function* render(target, pt, camera, progress = { current: 0, total: 0 }) {
426
459
  ray_direction.x, ray_direction.y, ray_direction.z
427
460
  );
428
461
 
429
- pt.path_trace(out, ray, Infinity, 4, random);
462
+ pt.path_trace(out, ray, Infinity, 16, random);
430
463
 
431
464
  out2[0] += out[0];
432
465
  out2[1] += out[1];
@@ -476,12 +509,12 @@ const camera = new PerspectiveCamera();
476
509
  async function start_renderer(camera) {
477
510
  camera.aspect = vCanvas.size.x / vCanvas.size.y;
478
511
 
479
- // const path = 'data/models/LowPolyTownshipSet/Small_house/Small_house.gltf';
480
- const path = 'data/models/road_bike/road_bike.gltf'; //large CAD-type model
512
+ const path = 'data/models/LowPolyTownshipSet/Small_house/Small_house.gltf';
513
+ // const path = 'data/models/road_bike/road_bike.gltf'; //large CAD-type model
481
514
 
482
515
  // await prepare_scene_lucy(pt, camera);
483
- // await prepare_scene_rtiow(pt, camera);
484
- await prepare_scene_sphere_01(pt, camera);
516
+ await prepare_scene_rtiow(pt, camera);
517
+ // await prepare_scene_sphere_01(pt, camera);
485
518
  // await prepare_scene_gltf(pt, camera, path);
486
519
 
487
520
  pt.build();
@@ -1,5 +1,32 @@
1
1
  import { v3_length } from "../../../../core/geom/v3_length.js";
2
2
 
3
+ /**
4
+ *
5
+ * @param {number[]} output
6
+ * @param {number} output_offset
7
+ * @param {number[]} normal
8
+ * @param {number} normal_offset
9
+ * @param {number[]} m 4x4 matrix
10
+ */
11
+ export function transform_normal_m4(output, output_offset, normal, normal_offset, m) {
12
+
13
+ const n_x = normal[normal_offset];
14
+ const n_y = normal[normal_offset + 1];
15
+ const n_z = normal[normal_offset + 2];
16
+
17
+ const result_n_x = m[0] * n_x + m[4] * n_y + m[8] * n_z;
18
+ const result_n_y = m[1] * n_x + m[5] * n_y + m[9] * n_z;
19
+ const result_n_z = m[2] * n_x + m[6] * n_y + m[10] * n_z;
20
+
21
+ const vec_length = v3_length(result_n_x, result_n_y, result_n_z);
22
+
23
+ const normal_multiplier = vec_length === 0 ? 0 : 1 / vec_length;
24
+
25
+ output[output_offset] = result_n_x * normal_multiplier;
26
+ output[output_offset + 1] = result_n_y * normal_multiplier;
27
+ output[output_offset + 2] = result_n_z * normal_multiplier;
28
+ }
29
+
3
30
  /**
4
31
  *
5
32
  * @param {number[]} output
@@ -25,18 +52,6 @@ export function ray_hit_apply_transform(output, hit, m) {
25
52
  output[2] = result_p_z;
26
53
 
27
54
  // transform normal
28
- const n_x = hit[3];
29
- const n_y = hit[4];
30
- const n_z = hit[5];
31
-
32
- const result_n_x = m[0] * n_x + m[4] * n_y + m[8] * n_z;
33
- const result_n_y = m[1] * n_x + m[5] * n_y + m[9] * n_z;
34
- const result_n_z = m[2] * n_x + m[6] * n_y + m[10] * n_z;
35
-
36
- const normal_multiplier = 1 / v3_length(result_n_x, result_n_y, result_n_z);
37
-
38
- output[3] = result_n_x * normal_multiplier;
39
- output[4] = result_n_y * normal_multiplier;
40
- output[5] = result_n_z * normal_multiplier;
55
+ transform_normal_m4(output, 3, hit, 3, m);
41
56
 
42
57
  }
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.43.20",
8
+ "version": "2.43.21",
9
9
  "dependencies": {
10
10
  "gl-matrix": "3.4.3",
11
11
  "fast-levenshtein": "2.0.6",