zincjs 2.4.1-beta.4 → 2.4.1-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/region.js CHANGED
@@ -2,6 +2,7 @@ import * as THREE from 'three/webgpu';
2
2
  import { Pointset } from './primitives/pointset';
3
3
  import { Lines2 } from './primitives/lines2';
4
4
  import { Geometry } from './primitives/geometry';
5
+ import { updateWorldMatrixFromAncestors } from './utilities';
5
6
 
6
7
  let uniqueiId = 0;
7
8
 
@@ -33,6 +34,10 @@ let Region = function (parentIn, sceneIn) {
33
34
  this.isRegion = true;
34
35
  this.uuid = getUniqueId();
35
36
 
37
+ //Ask the scene to draw the next frame, used by render on demand.
38
+ const requestRender = () => {
39
+ scene?.invalidate?.();
40
+ }
36
41
 
37
42
  /**
38
43
  * Hide all primitives belong to this region.
@@ -57,6 +62,7 @@ let Region = function (parentIn, sceneIn) {
57
62
  * @param {Boolean} flag - A flag indicating either the visibilty to be on/off.
58
63
  */
59
64
  this.setVisibility = (flag) => {
65
+ requestRender();
60
66
  if (flag != group.visible) {
61
67
  group.visible = flag;
62
68
  this.pickableUpdateRequired = true;
@@ -90,9 +96,17 @@ let Region = function (parentIn, sceneIn) {
90
96
  * used for the transformation.
91
97
  */
92
98
  this.setTransformation = transformation => {
99
+ requestRender();
93
100
  tMatrix.set(...transformation);
94
101
  group.matrix.copy(tMatrix);
95
- group.updateMatrixWorld();
102
+ //The group does not update its matrix automatically, compute its world
103
+ //matrix from the ancestors and update the descendants.
104
+ updateWorldMatrixFromAncestors(group);
105
+ group.updateMatrixWorld(true);
106
+ //Bounding boxes of everything in this region have moved
107
+ this.getAllObjects(true).forEach((zincObject) => {
108
+ zincObject.boundingBoxUpdateRequired = true;
109
+ });
96
110
  }
97
111
 
98
112
  /**
@@ -178,6 +192,7 @@ let Region = function (parentIn, sceneIn) {
178
192
  * @return {Region}
179
193
  */
180
194
  this.createChild = (nameIn) => {
195
+ requestRender();
181
196
  let childRegion = new Region(this, scene);
182
197
  childRegion.setName(nameIn);
183
198
  children.push(childRegion);
@@ -306,6 +321,7 @@ let Region = function (parentIn, sceneIn) {
306
321
  * this region.
307
322
  */
308
323
  this.addZincObject = zincObject => {
324
+ requestRender();
309
325
  if (zincObject) {
310
326
  zincObject.setRegion(this);
311
327
  group.add(zincObject.getGroup());
@@ -325,6 +341,7 @@ let Region = function (parentIn, sceneIn) {
325
341
  * @param {ZincObject} zincObject - object to be removed from this region.
326
342
  */
327
343
  this.removeZincObject = zincObject => {
344
+ requestRender();
328
345
  for (let i = 0; i < zincObjects.length; i++) {
329
346
  if (zincObject === zincObjects[i]) {
330
347
  group.remove(zincObject.getGroup());
@@ -359,11 +376,28 @@ let Region = function (parentIn, sceneIn) {
359
376
  return false;
360
377
  }
361
378
 
379
+ /**
380
+ * Clear the pickable update flag for this region and its descendants,
381
+ * used when a region is hidden and none of its objects are pickable.
382
+ *
383
+ * @private
384
+ */
385
+ this.clearPickableUpdateRequired = (transverse) => {
386
+ this.pickableUpdateRequired = false;
387
+ if (transverse) {
388
+ children.forEach(childRegion => {
389
+ childRegion.clearPickableUpdateRequired(transverse);
390
+ });
391
+ }
392
+ }
393
+
362
394
  /**
363
395
  * Get all pickable objects.
364
396
  */
365
397
  this.getPickableThreeJSObjects = (objectsList, transverse) => {
366
- if (group.visible) {
398
+ if (!group.visible) {
399
+ this.clearPickableUpdateRequired(transverse);
400
+ } else {
367
401
  zincObjects.forEach(zincObject => {
368
402
  if (zincObject.isPickable && zincObject.getGroup() && zincObject.getGroup().visible) {
369
403
  let marker = zincObject.marker;
@@ -411,30 +445,35 @@ let Region = function (parentIn, sceneIn) {
411
445
  * @returns {THREE.Box3}
412
446
  */
413
447
  this.getBoundingBox = transverse => {
414
- let boundingBox1 = undefined, boundingBox2 = undefined;
415
- zincObjects.forEach(zincObject => {
416
- boundingBox2 = zincObject.getBoundingBox();
417
- if (boundingBox2) {
418
- if (boundingBox1 == undefined) {
419
- boundingBox1 = boundingBox2.clone();
420
- } else {
421
- boundingBox1.union(boundingBox2);
422
- }
448
+ const boundingBox = new THREE.Box3();
449
+ if (this.accumulateBoundingBox(boundingBox, transverse)) {
450
+ return boundingBox;
451
+ }
452
+ return undefined;
453
+ }
454
+
455
+ /**
456
+ * Expand the target by the bounding box of this region.
457
+ *
458
+ * @return {Boolean} - true if any bounding box has been found.
459
+ * @private
460
+ */
461
+ this.accumulateBoundingBox = (target, transverse) => {
462
+ let found = false;
463
+ for (let i = 0; i < zincObjects.length; i++) {
464
+ const boundingBox = zincObjects[i].getBoundingBox();
465
+ if (boundingBox) {
466
+ target.union(boundingBox);
467
+ found = true;
423
468
  }
424
- });
469
+ }
425
470
  if (transverse) {
426
- children.forEach(childRegion => {
427
- boundingBox2 = childRegion.getBoundingBox(transverse);
428
- if (boundingBox2) {
429
- if (boundingBox1 == undefined) {
430
- boundingBox1 = boundingBox2.clone();
431
- } else {
432
- boundingBox1.union(boundingBox2);
433
- }
434
- }
435
- });
471
+ for (let i = 0; i < children.length; i++) {
472
+ if (children[i].accumulateBoundingBox(target, transverse))
473
+ found = true;
474
+ }
436
475
  }
437
- return boundingBox1;
476
+ return found;
438
477
  }
439
478
 
440
479
  /**
@@ -444,6 +483,7 @@ let Region = function (parentIn, sceneIn) {
444
483
  * if this is set to true.
445
484
  */
446
485
  this.clear = transverse => {
486
+ requestRender();
447
487
  if (transverse) {
448
488
  children.forEach(childRegion => {
449
489
  childRegion.clear(transverse);
@@ -669,13 +709,14 @@ let Region = function (parentIn, sceneIn) {
669
709
  * set to true.
670
710
  * @returns {Array}
671
711
  */
672
- this.getAllObjects = transverse => {
673
- const objectsArray = [...zincObjects];
712
+ this.getAllObjects = (transverse, objectsArray = []) => {
713
+ for (let i = 0; i < zincObjects.length; i++) {
714
+ objectsArray.push(zincObjects[i]);
715
+ }
674
716
  if (transverse) {
675
- children.forEach(childRegion => {
676
- let childObjects = childRegion.getAllObjects(transverse);
677
- objectsArray.push(...childObjects);
678
- });
717
+ for (let i = 0; i < children.length; i++) {
718
+ children[i].getAllObjects(transverse, objectsArray);
719
+ }
679
720
  }
680
721
  return objectsArray;
681
722
  }
@@ -730,7 +771,7 @@ let Region = function (parentIn, sceneIn) {
730
771
  });
731
772
  if (transverse) {
732
773
  children.forEach(childRegion => {
733
- childRegion.setMorphTime(time);
774
+ childRegion.setMorphTime(time, transverse);
734
775
  });
735
776
  }
736
777
  }
@@ -757,14 +798,23 @@ let Region = function (parentIn, sceneIn) {
757
798
 
758
799
  /**
759
800
  * Update geometries and glyphsets based on the calculated time.
801
+ * Render the objects in this region and optionally its descendants,
802
+ * the tree is walked in place to avoid allocations on every frame.
760
803
  * @private
761
804
  */
805
+ this.renderObjects = (delta, playAnimation, cameraControls, options, transverse) => {
806
+ for (let i = 0; i < zincObjects.length; i++) {
807
+ zincObjects[i].render(delta, playAnimation, cameraControls, options);
808
+ }
809
+ if (transverse) {
810
+ for (let i = 0; i < children.length; i++) {
811
+ children[i].renderObjects(delta, playAnimation, cameraControls, options, transverse);
812
+ }
813
+ }
814
+ }
815
+
762
816
  this.renderGeometries = (playRate, delta, playAnimation, cameraControls, options, transverse) => {
763
- // Let video dictates the progress if one is present
764
- const allObjects = this.getAllObjects(transverse);
765
- allObjects.forEach(zincObject => {
766
- zincObject.render(playRate * delta, playAnimation, cameraControls, options);
767
- });
817
+ this.renderObjects(playRate * delta, playAnimation, cameraControls, options, transverse);
768
818
  //process markers visibility and size, as long as there are more than
769
819
  //one entry in markersList is greater than 1, markers have been enabled.
770
820
  if (options && (playAnimation === false) &&
@@ -0,0 +1,20 @@
1
+ /*
2
+ * Frame requests from resources that do not belong to a scene, e.g. shared
3
+ * textures loaded asynchronously. Renderers in render on demand mode
4
+ * subscribe to these so the result is drawn once it arrives.
5
+ */
6
+ const listeners = new Set();
7
+
8
+ const onRenderRequest = (callback) => {
9
+ listeners.add(callback);
10
+ }
11
+
12
+ const offRenderRequest = (callback) => {
13
+ listeners.delete(callback);
14
+ }
15
+
16
+ const requestRenderAll = () => {
17
+ listeners.forEach((callback) => callback());
18
+ }
19
+
20
+ export { onRenderRequest, offRenderRequest, requestRenderAll };
package/src/renderer.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import * as THREE from 'three/webgpu';
2
2
  import { ResizeSensor } from 'css-element-queries';
3
3
  import { Scene } from './scene';
4
+ import { onRenderRequest, offRenderRequest } from './renderRequests';
5
+ import { setTextPixelsPerUnit } from './textSprite';
4
6
  /**
5
7
  * Create a Zinc 3D renderer in the container provided.
6
8
  * The primary function of a Zinc 3D renderer is to display the current
@@ -48,6 +50,15 @@ const Renderer = function (containerIn) {
48
50
  const _this = this;
49
51
  const currentSize = [0, 0];
50
52
  const currentOffset = [0, 0];
53
+ //Render on demand is opt-in, when enabled the scene is only drawn when
54
+ //something has changed.
55
+ let renderOnDemand = false;
56
+ let needsRender = true;
57
+ const requestRender = () => {
58
+ needsRender = true;
59
+ }
60
+ //Camera of the current scene shared with additional active scenes
61
+ const sharedCamera = { controls: undefined, updated: false };
51
62
 
52
63
  this.getDrawingWidth = () => {
53
64
  if (container) {
@@ -75,6 +86,7 @@ const Renderer = function (containerIn) {
75
86
  * Call this to resize the renderer, this is normally call automatically.
76
87
  */
77
88
  this.onWindowResize = () => {
89
+ needsRender = true;
78
90
  currentScene.onWindowResize();
79
91
  //Give it a miniumum size of 1 x 1
80
92
  const width = this.getDrawingWidth() || 1;
@@ -103,6 +115,12 @@ const Renderer = function (containerIn) {
103
115
  renderer.getSize(target);
104
116
  currentSize[0] = target.x;
105
117
  currentSize[1] = target.y;
118
+ //Text sprites are drawn at the resolution they occupy on screen
119
+ const fov = currentScene?.camera?.fov;
120
+ if (fov) {
121
+ setTextPixelsPerUnit(currentSize[1] * renderer.getPixelRatio() /
122
+ (2 * Math.tan(fov * Math.PI / 360)));
123
+ }
106
124
  }
107
125
  }
108
126
 
@@ -134,10 +152,10 @@ const Renderer = function (containerIn) {
134
152
  //WebGPURenderer otherwise requests a device with the default
135
153
  //maxTextureArrayLayers (256 on many adapters), which is too low for
136
154
  //some currnetly sypported scaffold, and the default
137
- //maxStorageBuffersPerShaderStage (8 on many adapters), which is too
138
- //low for compute passes like glyphset.js's GPU glyph-transform (11
139
- //storage buffers). We will request the highest possible on the device
140
- //for both.
155
+ //maxStorageBuffersPerShaderStage (8 on many adapters). The glyph
156
+ //transform compute pass only needs two storage buffers, the higher
157
+ //limit is kept as headroom. We will request the highest possible on
158
+ //the device for both.
141
159
  if (parameters.device === undefined && parameters.requiredLimits === undefined &&
142
160
  typeof navigator !== 'undefined' && navigator.gpu) {
143
161
  try {
@@ -172,6 +190,7 @@ const Renderer = function (containerIn) {
172
190
  renderer.autoClear = false;
173
191
  const scene = this.createScene("default");
174
192
  this.setCurrentScene(scene);
193
+ onRenderRequest(requestRender);
175
194
  isInitialised = true;
176
195
  }
177
196
  }
@@ -218,6 +237,7 @@ const Renderer = function (containerIn) {
218
237
  }
219
238
  currentScene.setInteractiveControlEnable(true);
220
239
  currentScene.setAdditionalScenesGroup(scenesGroup);
240
+ needsRender = true;
221
241
  this.onWindowResize();
222
242
  }
223
243
  }
@@ -377,6 +397,7 @@ const Renderer = function (containerIn) {
377
397
  if (!isRendering) {
378
398
  clock.reset();
379
399
  isRendering = true;
400
+ needsRender = true;
380
401
  runAnimation();
381
402
  }
382
403
  }
@@ -486,6 +507,41 @@ const Renderer = function (containerIn) {
486
507
  */
487
508
  this.setPlayRate = playRateIn => {
488
509
  playRate = playRateIn;
510
+ needsRender = true;
511
+ }
512
+
513
+ /**
514
+ * Enable or disable render on demand, it is disabled by default.
515
+ * When enabled, the scenes are only drawn when something has changed,
516
+ * e.g. camera movement, animation, loading and changes made through
517
+ * the Zinc APIs. Call {@link Renderer#invalidate} after modifying
518
+ * THREE.js objects directly or after changing the THREE.js renderer,
519
+ * e.g. setClearColor.
520
+ * Pre-render callbacks are called on every frame, post-render callbacks
521
+ * are only called when the scenes are drawn.
522
+ *
523
+ * @param {Boolean} flag - Enable or disable render on demand.
524
+ */
525
+ this.setRenderOnDemand = flag => {
526
+ renderOnDemand = flag ? true : false;
527
+ needsRender = true;
528
+ }
529
+
530
+ /**
531
+ * Check if render on demand is enabled.
532
+ *
533
+ * @return {Boolean}
534
+ */
535
+ this.isRenderOnDemand = () => {
536
+ return renderOnDemand;
537
+ }
538
+
539
+ /**
540
+ * Request the scenes to be drawn on the next frame, this is only
541
+ * required when render on demand is enabled.
542
+ */
543
+ this.invalidate = () => {
544
+ needsRender = true;
489
545
  }
490
546
 
491
547
  this.getCurrentTime = () => {
@@ -534,6 +590,7 @@ const Renderer = function (containerIn) {
534
590
  cameraOrtho.position.z = 10;
535
591
  }
536
592
  sceneOrtho.add(object)
593
+ needsRender = true;
537
594
  }
538
595
 
539
596
  const createHUDSprites = logoSprite => {
@@ -575,22 +632,33 @@ const Renderer = function (containerIn) {
575
632
  }
576
633
  clock.update();
577
634
  const delta = clock.getDelta();
578
- currentScene.renderGeometries(playRate, delta, this.playAnimation);
635
+ let changed = currentScene.renderGeometries(playRate, delta, this.playAnimation);
636
+ if (additionalActiveScenes.length > 0) {
637
+ //Additional scenes are displayed with the current scene's camera
638
+ sharedCamera.controls = currentScene.getZincCameraControls();
639
+ sharedCamera.updated = currentScene.isCameraUpdated();
640
+ }
579
641
  for(let i = 0; i < additionalActiveScenes.length; i++) {
580
642
  const sceneItem = additionalActiveScenes[i];
581
- sceneItem.renderGeometries(playRate, delta, this.playAnimation);
643
+ if (sceneItem.renderGeometries(playRate, delta, this.playAnimation, sharedCamera))
644
+ changed = true;
582
645
  }
583
- if (cameraOrtho != undefined && sceneOrtho != undefined) {
584
- renderer.clearDepth();
585
- renderer.render( sceneOrtho, cameraOrtho );
586
- }
587
- for (let key of Object.keys(preRenderCallbackFunctions)) {
646
+ for (let key in preRenderCallbackFunctions) {
588
647
  if (preRenderCallbackFunctions.hasOwnProperty(key)) {
589
648
  preRenderCallbackFunctions[key].call();
590
649
  }
591
650
  }
651
+ //Pre-render callbacks may have requested a frame
652
+ if (renderOnDemand && !changed && !needsRender) {
653
+ return;
654
+ }
655
+ needsRender = false;
592
656
  currentScene.render(renderer);
593
- for (let key of Object.keys(postRenderCallbackFunctions)) {
657
+ if (cameraOrtho != undefined && sceneOrtho != undefined) {
658
+ renderer.clearDepth();
659
+ renderer.render( sceneOrtho, cameraOrtho );
660
+ }
661
+ for (let key in postRenderCallbackFunctions) {
594
662
  if (postRenderCallbackFunctions.hasOwnProperty(key)) {
595
663
  postRenderCallbackFunctions[key].call();
596
664
  }
@@ -639,6 +707,7 @@ const Renderer = function (containerIn) {
639
707
  if (!this.isSceneActive(additionalScene)) {
640
708
  additionalActiveScenes.push(additionalScene);
641
709
  scenesGroup.add(additionalScene.getThreeJSScene());
710
+ needsRender = true;
642
711
  }
643
712
  }
644
713
 
@@ -653,6 +722,7 @@ const Renderer = function (containerIn) {
653
722
  if (sceneItem === additionalScene) {
654
723
  additionalActiveScenes.splice(i, 1);
655
724
  scenesGroup.remove(additionalScene.getThreeJSScene());
725
+ needsRender = true;
656
726
  return;
657
727
  }
658
728
  }
@@ -666,6 +736,7 @@ const Renderer = function (containerIn) {
666
736
  scenesGroup.remove(additionalActiveScenes[i].getThreeJSScene());
667
737
  }
668
738
  additionalActiveScenes.splice(0,additionalActiveScenes.length);
739
+ needsRender = true;
669
740
  }
670
741
 
671
742
  /**
@@ -686,7 +757,7 @@ const Renderer = function (containerIn) {
686
757
  preRenderCallbackFunctions = {};
687
758
  preRenderCallbackFunctions_id = 0;
688
759
  postRenderCallbackFunctions = {};
689
- postRenderCallbackFunctions = 0;
760
+ postRenderCallbackFunctions_id = 0;
690
761
  contextLostCallbackFunctions = {};
691
762
  contextLostCallbackFunctions_id = 0;
692
763
  contextRestoredCallbackFunctions = {};
@@ -697,6 +768,7 @@ const Renderer = function (containerIn) {
697
768
  const scene = this.createScene("default");
698
769
  this.setCurrentScene(scene);
699
770
  sensor = undefined;
771
+ offRenderRequest(requestRender);
700
772
  renderer?.dispose();
701
773
  }
702
774