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/scene.js CHANGED
@@ -47,6 +47,21 @@ const Scene = function (containerIn, rendererIn) {
47
47
  const container = containerIn;
48
48
  let cameraHelper = undefined;
49
49
  let videoHandler = undefined;
50
+ //Set when something has changed and the scene needs to be drawn,
51
+ //used for render on demand.
52
+ let needsRender = true;
53
+ //Set when the camera has been updated on the current frame
54
+ let cameraUpdated = false;
55
+ //Options passed to the objects on every frame
56
+ const renderOptions = {};
57
+ //Values of the display properties on the last frame, changing them
58
+ //requires the scene to be drawn.
59
+ const lastDisplayState = {
60
+ displayMarkers: undefined,
61
+ displayMinimap: undefined,
62
+ displayMiniAxes: undefined,
63
+ playAnimation: undefined,
64
+ };
50
65
  let sceneLoader = new SceneLoader(this);
51
66
  let minimap = undefined;
52
67
  let zincObjectAddedCallbacks = {};
@@ -130,6 +145,7 @@ const Scene = function (containerIn, rendererIn) {
130
145
 
131
146
  //called from Renderer when panel has been resized
132
147
  this.onWindowResize = () => {
148
+ needsRender = true;
133
149
  const wHeight = getDrawingHeight();
134
150
  this.camera.aspect = getDrawingWidth() / wHeight;
135
151
  this.camera.updateProjectionMatrix();
@@ -142,6 +158,7 @@ const Scene = function (containerIn, rendererIn) {
142
158
  * Reset the viewport of this scene to its original state.
143
159
  */
144
160
  this.resetView = () => {
161
+ needsRender = true;
145
162
  this.onWindowResize();
146
163
  zincCameraControls.resetView();
147
164
  }
@@ -150,6 +167,7 @@ const Scene = function (containerIn, rendererIn) {
150
167
  * Set the zoom level by unit scroll rate
151
168
  */
152
169
  this.changeZoomByScrollRateUnit = unit => {
170
+ needsRender = true;
153
171
  zincCameraControls.changeZoomByScrollRateUnit(unit);
154
172
  }
155
173
 
@@ -178,6 +196,7 @@ const Scene = function (containerIn, rendererIn) {
178
196
  * @param {Zinc.Viewport} viewData - Viewport data to be loaded.
179
197
  */
180
198
  this.loadView = settings => {
199
+ needsRender = true;
181
200
  const viewPort = new Viewport();
182
201
  viewPort.setFromObject(settings);
183
202
  zincCameraControls.setCurrentCameraSettings(viewPort);
@@ -214,6 +233,7 @@ const Scene = function (containerIn, rendererIn) {
214
233
  * which we the viewport should be displaying.
215
234
  */
216
235
  this.viewAllWithBoundingBox = boundingBox => {
236
+ needsRender = true;
217
237
  if (boundingBox) {
218
238
  const viewport = zincCameraControls.getViewportFromBoundingBox(boundingBox, 1.0);
219
239
  zincCameraControls.setCurrentCameraSettings(viewport);
@@ -226,6 +246,7 @@ const Scene = function (containerIn, rendererIn) {
226
246
  * Adjust zoom distance to include all primitives in scene only.
227
247
  */
228
248
  this.viewAll = () => {
249
+ needsRender = true;
229
250
  const boundingBox = this.getBoundingBox();
230
251
  this.viewAllWithBoundingBox(boundingBox);
231
252
  markerCluster.markerUpdateRequired = true;
@@ -400,6 +421,7 @@ const Scene = function (containerIn, rendererIn) {
400
421
  * @returns {THREE.Vector3}
401
422
  */
402
423
  this.addZincObject = zincObject => {
424
+ needsRender = true;
403
425
  if (zincObject) {
404
426
  rootRegion.addZincObject(zincObject);
405
427
  if (zincCameraControls)
@@ -530,6 +552,7 @@ const Scene = function (containerIn, rendererIn) {
530
552
 
531
553
  //Update the directional light for this scene.
532
554
  this.updateDirectionalLight = () => {
555
+ needsRender = true;
533
556
  zincCameraControls.updateDirectionalLight();
534
557
  }
535
558
 
@@ -538,6 +561,7 @@ const Scene = function (containerIn, rendererIn) {
538
561
  * @param {THREE.Object} object - to be addded into this scene.
539
562
  */
540
563
  this.addObject = object => {
564
+ needsRender = true;
541
565
  scene.add(object);
542
566
  }
543
567
 
@@ -546,6 +570,7 @@ const Scene = function (containerIn, rendererIn) {
546
570
  * @param {THREE.Object} object - to be removed from this scene.
547
571
  */
548
572
  this.removeObject = object => {
573
+ needsRender = true;
549
574
  scene.remove(object);
550
575
  }
551
576
 
@@ -569,6 +594,7 @@ const Scene = function (containerIn, rendererIn) {
569
594
  * @param {Number} time - Value to set the time to.
570
595
  */
571
596
  this.setMorphsTime = (time) => {
597
+ needsRender = true;
572
598
  if (videoHandler != undefined) {
573
599
  videoHandler.setMorphTime(time, duration);
574
600
  }
@@ -591,16 +617,22 @@ const Scene = function (containerIn, rendererIn) {
591
617
  * Update geometries and glyphsets based on the calculated time.
592
618
  * @private
593
619
  */
594
- this.renderGeometries = (playRate, delta, playAnimation) => {
595
- // Let video dictates the progress if one is present
596
- let options = {};
597
- options.camera = zincCameraControls;
620
+ this.renderGeometries = (playRate, delta, playAnimation, sharedCamera) => {
621
+ //Anything requested since the last frame
622
+ let changed = needsRender;
623
+ needsRender = false;
624
+ cameraUpdated = false;
625
+ //Additional active scenes are displayed with the current scene's camera
626
+ const cameraControls = sharedCamera ? sharedCamera.controls : zincCameraControls;
627
+ //Reused on every frame to avoid allocations
628
+ const options = renderOptions;
629
+ options.camera = cameraControls;
598
630
  //Global markers flag, marker can be set at individual zinc object level
599
631
  //overriding this flag.
600
632
  options.displayMarkers = this.displayMarkers;
601
633
  options.markerCluster = markerCluster;
602
634
  options.markersList = markerCluster.markers;
603
- options.ndcToBeUpdated = false;
635
+ options.ndcToBeUpdated = sharedCamera ? sharedCamera.updated : false;
604
636
  //Always set marker cluster update required when playAnimation is true
605
637
  //to make sure it is updated when it stops
606
638
  if (playAnimation) {
@@ -616,29 +648,90 @@ const Scene = function (containerIn, rendererIn) {
616
648
  const currentTime = videoHandler.video.currentTime /
617
649
  videoHandler.getVideoDuration() * duration;
618
650
  if (0 == sceneLoader.toBeDownloaded) {
619
- zincCameraControls.setTime(currentTime);
620
- options.ndcToBeUpdated = zincCameraControls.update(0);
621
- if (options.ndcToBeUpdated) {
622
- zincCameraControls.calculateHeightPerPixelAtZeroDepth(getDrawingHeight());
651
+ if (!sharedCamera) {
652
+ zincCameraControls.setTime(currentTime);
653
+ options.ndcToBeUpdated = zincCameraControls.update(0);
654
+ if (options.ndcToBeUpdated) {
655
+ zincCameraControls.calculateHeightPerPixelAtZeroDepth(getDrawingHeight());
656
+ }
623
657
  }
624
658
  rootRegion.setMorphTime(currentTime, true);
625
- rootRegion.renderGeometries(0, 0, playAnimation, zincCameraControls, options, true);
626
- } else {
627
- zincCameraControls.update(0);
659
+ rootRegion.renderGeometries(0, 0, playAnimation, cameraControls, options, true);
660
+ } else if (!sharedCamera && zincCameraControls.update(0)) {
661
+ changed = true;
662
+ cameraUpdated = true;
628
663
  }
629
664
  //console.log(videoHandler.video.currentTime / videoHandler.getVideoDuration() * 6000);
630
665
  }
666
+ //The video texture changes while playing or loading
667
+ if (playAnimation || !videoHandler.isReadyToPlay())
668
+ changed = true;
631
669
  } else {
632
670
  if (0 == sceneLoader.toBeDownloaded) {
633
- options.ndcToBeUpdated = zincCameraControls.update(delta);
634
- if (options.ndcToBeUpdated) {
635
- zincCameraControls.calculateHeightPerPixelAtZeroDepth(getDrawingHeight());
671
+ if (!sharedCamera) {
672
+ options.ndcToBeUpdated = zincCameraControls.update(delta);
673
+ if (options.ndcToBeUpdated) {
674
+ zincCameraControls.calculateHeightPerPixelAtZeroDepth(getDrawingHeight());
675
+ }
636
676
  }
637
- rootRegion.renderGeometries(playRate, delta, playAnimation, zincCameraControls, options, true);
638
- } else {
639
- zincCameraControls.update(0);
677
+ rootRegion.renderGeometries(playRate, delta, playAnimation, cameraControls, options, true);
678
+ } else if (!sharedCamera && zincCameraControls.update(0)) {
679
+ changed = true;
680
+ cameraUpdated = true;
640
681
  }
641
682
  }
683
+ //These are plain properties, check if they have changed
684
+ if (lastDisplayState.displayMarkers !== this.displayMarkers ||
685
+ lastDisplayState.displayMinimap !== this.displayMinimap ||
686
+ lastDisplayState.displayMiniAxes !== this.displayMiniAxes ||
687
+ lastDisplayState.playAnimation !== playAnimation) {
688
+ lastDisplayState.displayMarkers = this.displayMarkers;
689
+ lastDisplayState.displayMinimap = this.displayMinimap;
690
+ lastDisplayState.displayMiniAxes = this.displayMiniAxes;
691
+ lastDisplayState.playAnimation = playAnimation;
692
+ changed = true;
693
+ }
694
+ //The flag is only cleared when the minimap is drawn
695
+ if (this.minimapScissor.updateRequired &&
696
+ (this.displayMinimap || this.displayMiniAxes)) {
697
+ changed = true;
698
+ }
699
+ if (!sharedCamera && options.ndcToBeUpdated)
700
+ cameraUpdated = true;
701
+ if (options.ndcToBeUpdated ||
702
+ (playAnimation && this.isTimeVarying()) ||
703
+ //Objects appear as they are loaded
704
+ sceneLoader.toBeDownloaded > 0 ||
705
+ //Clusters are recalculated on a later frame
706
+ (markerCluster.isEnabled() && markerCluster.markerUpdateRequired)) {
707
+ changed = true;
708
+ }
709
+ //Changes made during this frame, e.g. LOD switching
710
+ if (needsRender) {
711
+ changed = true;
712
+ needsRender = false;
713
+ }
714
+ return changed;
715
+ }
716
+
717
+ /**
718
+ * Request this scene to be drawn on the next frame, this is only
719
+ * required when render on demand is enabled on the renderer.
720
+ * See {@link Renderer#setRenderOnDemand}.
721
+ */
722
+ this.invalidate = () => {
723
+ needsRender = true;
724
+ }
725
+
726
+ /**
727
+ * Check if the camera of this scene has been updated during the last
728
+ * call to renderGeometries.
729
+ *
730
+ * @return {Boolean}
731
+ * @private
732
+ */
733
+ this.isCameraUpdated = () => {
734
+ return cameraUpdated;
642
735
  }
643
736
 
644
737
  /**
@@ -650,6 +743,7 @@ const Scene = function (containerIn, rendererIn) {
650
743
  }
651
744
 
652
745
  this.setVideoHandler = (videoHandlerIn) => {
746
+ needsRender = true;
653
747
  if (!videoHandler)
654
748
  videoHandler = videoHandlerIn;
655
749
  }
@@ -770,6 +864,7 @@ const Scene = function (containerIn, rendererIn) {
770
864
  * @param {Number} durationIn - duration of the scene.
771
865
  */
772
866
  this.setDuration = durationIn => {
867
+ needsRender = true;
773
868
  rootRegion.setDuration(durationIn);
774
869
  duration = durationIn;
775
870
  zincCameraControls.setPathDuration(durationIn);
@@ -790,6 +885,7 @@ const Scene = function (containerIn, rendererIn) {
790
885
  * should be enabled or disabled.
791
886
  */
792
887
  this.setStereoEffectEnable = stereoFlag => {
888
+ needsRender = true;
793
889
  if (stereoFlag == true) {
794
890
  if (!stereoEffect) {
795
891
  stereoEffect = new StereoEffect(rendererIn);
@@ -819,6 +915,7 @@ const Scene = function (containerIn, rendererIn) {
819
915
  * @param {Number} transitionTime - Duration to perform the transition.
820
916
  */
821
917
  this.alignBoundingBoxToCameraView = (boundingBox, transitionTime) => {
918
+ needsRender = true;
822
919
  if (boundingBox) {
823
920
  boundingBox.getCenter(_v3);
824
921
  const viewport = this.getZincCameraControls().getCurrentViewport();
@@ -886,6 +983,7 @@ const Scene = function (containerIn, rendererIn) {
886
983
  * @param {ZincObject} zincObject - the bounding box to target
887
984
  */
888
985
  this.setCameraTargetToObject = zincObject => {
986
+ needsRender = true;
889
987
  if (this.objectIsInScene(zincObject)) {
890
988
  const boundingBox = zincObject.getBoundingBox();
891
989
  const viewport = this.getZincCameraControls().getCurrentViewport();
@@ -923,6 +1021,7 @@ const Scene = function (containerIn, rendererIn) {
923
1021
  * @param {Zinc.Object} zincObject - object to be removed from this scene.
924
1022
  */
925
1023
  this.removeZincObject = zincObject => {
1024
+ needsRender = true;
926
1025
  rootRegion.removeZincObject(zincObject);
927
1026
  if (zincCameraControls) {
928
1027
  zincCameraControls.calculateMaxAllowedDistance(this);
@@ -1021,6 +1120,7 @@ const Scene = function (containerIn, rendererIn) {
1021
1120
  * This does not remove obejcts that are added using the addObject APIs.
1022
1121
  */
1023
1122
  this.clearAll = () => {
1123
+ needsRender = true;
1024
1124
  markerCluster.clear();
1025
1125
  rootRegion.clear(true);
1026
1126
  this.clearZincObjectAddedCallbacks();
@@ -1259,6 +1359,7 @@ const Scene = function (containerIn, rendererIn) {
1259
1359
  * with clearTemporaryPrimitives method.
1260
1360
  */
1261
1361
  this.addTemporaryPoints = (coords, colour) => {
1362
+ needsRender = true;
1262
1363
  const geometry = createPointQuadGeometry(coords.length);
1263
1364
  const material = createInstancedPointsMaterial(getCircularTexture());
1264
1365
  material.color.set(colour);
@@ -1266,13 +1367,11 @@ const Scene = function (containerIn, rendererIn) {
1266
1367
  material.userData.uniforms.pointSize.value = 15;
1267
1368
  material.alphaTest = 0.5;
1268
1369
  const point = new InstancedPoints(geometry, material, coords.length);
1269
- point.pointPositions = new Float32Array(coords.length * 3);
1270
1370
  const instancePosition = geometry.getAttribute('instancePosition');
1371
+ //Share the array with the instance positions
1372
+ point.pointPositions = instancePosition.array;
1271
1373
  coords.forEach((coord, index) => {
1272
1374
  instancePosition.setXYZ(index, coord[0], coord[1], coord[2]);
1273
- point.pointPositions[index * 3] = coord[0];
1274
- point.pointPositions[index * 3 + 1] = coord[1];
1275
- point.pointPositions[index * 3 + 2] = coord[2];
1276
1375
  });
1277
1376
  instancePosition.needsUpdate = true;
1278
1377
  tempGroup.add(point);
@@ -1284,6 +1383,7 @@ const Scene = function (containerIn, rendererIn) {
1284
1383
  * with clearTemporaryPrimitives method.
1285
1384
  */
1286
1385
  this.addTemporaryLines = (coords, colour) => {
1386
+ needsRender = true;
1287
1387
  const geometry = createBufferGeometry(coords.length, coords);
1288
1388
  const material = new THREE.LineBasicMaterial({color:colour});
1289
1389
  const line = new LineSegments(geometry, material);
@@ -1296,6 +1396,7 @@ const Scene = function (containerIn, rendererIn) {
1296
1396
  * Display frustum
1297
1397
  */
1298
1398
  this.enableFrustumDisplay = () => {
1399
+ needsRender = true;
1299
1400
  if (this.camera && !cameraHelper) {
1300
1401
  cameraHelper = new THREE.CameraHelper(this.camera);
1301
1402
  scene.add(cameraHelper);
@@ -1306,6 +1407,7 @@ const Scene = function (containerIn, rendererIn) {
1306
1407
  * Hide frustum
1307
1408
  */
1308
1409
  this.disableFrustumDisplay = () => {
1410
+ needsRender = true;
1309
1411
  if (cameraHelper) {
1310
1412
  scene.remove(cameraHelper);
1311
1413
  cameraHelper.dispose();
@@ -1317,6 +1419,7 @@ const Scene = function (containerIn, rendererIn) {
1317
1419
  * Remove object from temporary objects list
1318
1420
  */
1319
1421
  this.removeTemporaryPrimitive = (object) => {
1422
+ needsRender = true;
1320
1423
  tempGroup.remove(object);
1321
1424
  object.geometry.dispose();
1322
1425
  object.material.dispose();
@@ -1327,6 +1430,7 @@ const Scene = function (containerIn, rendererIn) {
1327
1430
  * Return number of primitives removed;
1328
1431
  */
1329
1432
  this.clearTemporaryPrimitives = () => {
1433
+ needsRender = true;
1330
1434
  let i = 0;
1331
1435
  const children = tempGroup.children;
1332
1436
  children.forEach(child => {
@@ -1344,6 +1448,7 @@ const Scene = function (containerIn, rendererIn) {
1344
1448
  */
1345
1449
  this.addBoundingBoxPrimitive = (regionPath, group, colour, opacity,
1346
1450
  visibility, boundingBox = undefined) => {
1451
+ needsRender = true;
1347
1452
  let region = rootRegion.findChildFromPath(regionPath);
1348
1453
  if (region === undefined) {
1349
1454
  region = rootRegion.createChildFromPath(regionPath);
@@ -1365,6 +1470,7 @@ const Scene = function (containerIn, rendererIn) {
1365
1470
  */
1366
1471
  this.addSlicesPrimitive = (regionPath, groups, colours, opacity,
1367
1472
  visibility, boundingBox = undefined) => {
1473
+ needsRender = true;
1368
1474
  if (groups && groups.length >= 3 &&
1369
1475
  colours && colours.length >= 3) {
1370
1476
  let region = rootRegion.findChildFromPath(regionPath);
@@ -1418,6 +1524,7 @@ const Scene = function (containerIn, rendererIn) {
1418
1524
  * Enable marker cluster to work with markers
1419
1525
  */
1420
1526
  this.enableMarkerCluster = (flag) => {
1527
+ needsRender = true;
1421
1528
  if (flag) {
1422
1529
  markerCluster.markerUpdateRequired = true;
1423
1530
  markerCluster.enable();
@@ -1432,6 +1539,7 @@ const Scene = function (containerIn, rendererIn) {
1432
1539
  * Destory static axis display object
1433
1540
  */
1434
1541
  this.destroyAxisDisplay = () => {
1542
+ needsRender = true;
1435
1543
  this.displayMiniAxes = false;
1436
1544
  if (axisDisplay.main) {
1437
1545
  this.enableAxisDisplay(false, false);
@@ -1459,6 +1567,7 @@ const Scene = function (containerIn, rendererIn) {
1459
1567
  * Create static axis display object
1460
1568
  */
1461
1569
  this.createAxisDisplay = (fitBoundingBox = false) => {
1570
+ needsRender = true;
1462
1571
  this.destroyAxisDisplay();
1463
1572
  const XYZ = [
1464
1573
  {
@@ -1511,6 +1620,7 @@ const Scene = function (containerIn, rendererIn) {
1511
1620
  * before the axis can be display.
1512
1621
  */
1513
1622
  this.enableAxisDisplay = (enable, miniaxes = false) => {
1623
+ needsRender = true;
1514
1624
  if (miniaxes && axisDisplay?.mini?.length) {
1515
1625
  this.displayMiniAxes = enable;
1516
1626
  axisDisplay.mini.forEach(axis => {
@@ -1,5 +1,6 @@
1
1
  import { BufferAttribute } from 'three/webgpu';
2
2
  import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter';
3
+ import { useStandardIndexType } from './utilities';
3
4
 
4
5
  /*
5
6
  * GLTFExporter only writes POSITION and NORMAL morph targets. glTF allows
@@ -89,6 +90,8 @@ const SceneExporter = function (sceneIn) {
89
90
  glyphsetsToRestore.push(zincObject);
90
91
  }
91
92
  }
93
+ //GLTFExporter only accepts plain typed arrays
94
+ const restoreIndexType = useStandardIndexType(scene.getThreeJSScene());
92
95
  const exporter = new GLTFExporter();
93
96
  exporter.register(writer => new GLTFMorphColourPlugin(writer));
94
97
  const options = { binary, animations, onlyVisible: true };
@@ -106,6 +109,7 @@ const SceneExporter = function (sceneIn) {
106
109
  options );
107
110
  });
108
111
  } finally {
112
+ restoreIndexType();
109
113
  glyphsetsToRestore.forEach(zincObject => zincObject.clearColorExportState());
110
114
  }
111
115
  }
@@ -27,8 +27,6 @@ const SceneLoader = function (sceneIn) {
27
27
  this.progressMap = {};
28
28
  let viewLoaded = false;
29
29
  let errorDownload = false;
30
- //Incremented when pending loads are cancelled, loads requested
31
- //before the increment are discarded once they complete.
32
30
  let generation = 0;
33
31
  const primitivesLoader = new PrimitivesLoader();
34
32
 
@@ -450,7 +448,8 @@ const SceneLoader = function (sceneIn) {
450
448
  const loader = new STLLoader();
451
449
  loader.crossOrigin = "Anonymous";
452
450
  loader.load(url, meshloader(region, colour, opacity, false,
453
- false, groupName, undefined, undefined, undefined, finishCallback));
451
+ false, groupName, undefined, undefined, {}, finishCallback),
452
+ this.onProgress(url), this.onError(finishCallback));
454
453
  }
455
454
 
456
455
  /**
@@ -468,8 +467,9 @@ const SceneLoader = function (sceneIn) {
468
467
  const opacity = Zinc.defaultOpacity;
469
468
  const loader = new OBJLoader();
470
469
  loader.crossOrigin = "Anonymous";
471
- loader.load(url, meshloader(region, colour, opacity, false,
472
- false, groupName, undefined, undefined, undefined, finishCallback));
470
+ loader.load(url, objloader(region, colour, opacity, false,
471
+ false, groupName, undefined, undefined, {}, finishCallback),
472
+ this.onProgress(url), this.onError(finishCallback));
473
473
  }
474
474
 
475
475
  /**
@@ -500,17 +500,14 @@ const SceneLoader = function (sceneIn) {
500
500
  if (morphColour != undefined)
501
501
  localMorphColour = morphColour ? true : false;
502
502
  let loader = primitivesLoader;
503
- if (fileFormat !== undefined) {
504
- if (fileFormat == "STL") {
505
- loader = new STLLoader();
506
- } else if (fileFormat == "OBJ") {
507
- loader = new OBJLoader();
508
- loader.crossOrigin = "Anonymous";
509
- loader.load(url, objloader(region, colour, opacity, localTimeEnabled,
510
- localMorphColour, groupName, anatomicalId, finishCallback), this.onProgress(url), this.onError,
511
- options.loaderOptions);
512
- return;
513
- }
503
+ if (fileFormat == "STL" || fileFormat == "OBJ") {
504
+ const typedLoader = fileFormat == "STL" ? meshloader : objloader;
505
+ loader = fileFormat == "STL" ? new STLLoader() : new OBJLoader();
506
+ loader.crossOrigin = "Anonymous";
507
+ loader.load(url, typedLoader(region, colour, opacity, localTimeEnabled,
508
+ localMorphColour, groupName, anatomicalId, renderOrder, options ? options : {},
509
+ finishCallback), this.onProgress(url), this.onError(finishCallback));
510
+ return;
514
511
  }
515
512
  if (isInline) {
516
513
  const object = primitivesLoader.parse( url );
@@ -723,7 +720,7 @@ const SceneLoader = function (sceneIn) {
723
720
  }
724
721
  const zincGeometry = addZincGeometry(region, geometry, colour, opacity,
725
722
  localTimeEnabled, localMorphColour, undefined, material, groupName, renderOrder, anatomicalId);
726
- if (options.lod && options.lod.levels) {
723
+ if (options?.lod?.levels) {
727
724
  for (const [key, value] of Object.entries(options.lod.levels)) {
728
725
  zincGeometry.addLOD(primitivesLoader, key, value.URL, value.Index, options.lod.preload);
729
726
  }
@@ -736,6 +733,48 @@ const SceneLoader = function (sceneIn) {
736
733
  };
737
734
  }
738
735
 
736
+ //Internal loader for OBJ files, OBJLoader returns a group and each mesh
737
+ //in it is added as a separate zinc geometry.
738
+ const objloader = (
739
+ region,
740
+ colour,
741
+ opacity,
742
+ localTimeEnabled,
743
+ localMorphColour,
744
+ groupName,
745
+ anatomicalId,
746
+ renderOrder,
747
+ options,
748
+ finishCallback
749
+ ) => {
750
+ const isStale = createStaleCheck();
751
+ const onMeshLoaded = meshloader(region, colour, opacity, localTimeEnabled,
752
+ localMorphColour, groupName, anatomicalId, renderOrder, options, finishCallback);
753
+ return (group) => {
754
+ const meshes = [];
755
+ group.traverse((child) => {
756
+ if (child.isMesh && child.geometry) {
757
+ meshes.push(child);
758
+ }
759
+ if (child.material) {
760
+ const materials = Array.isArray(child.material) ? child.material : [child.material];
761
+ materials.forEach((material) => material.dispose());
762
+ }
763
+ });
764
+ if (isStale()) {
765
+ meshes.forEach((mesh) => mesh.geometry.dispose());
766
+ return;
767
+ }
768
+ if (meshes.length === 0) {
769
+ --this.toBeDownloaded;
770
+ return;
771
+ }
772
+ //meshloader decrements the counter once per mesh
773
+ this.toBeDownloaded += meshes.length - 1;
774
+ meshes.forEach((mesh) => onMeshLoaded(mesh.geometry));
775
+ };
776
+ }
777
+
739
778
  //Turn ISO 8601 duration string into an array.
740
779
  const parseDuration = (durationString) => {
741
780
  const regex = /P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
@@ -1017,9 +1056,9 @@ const SceneLoader = function (sceneIn) {
1017
1056
  // view file does not receive callback
1018
1057
  let callback = new metaFinishCallback(numberOfObjects, finishCallback, allCompletedCallback);
1019
1058
  // Prioritise the view file and settings before loading anything else
1020
- for (let i = 0; i < metadata.length; i++)
1059
+ for (let i = 0; i < filteredMetada.length; i++)
1021
1060
  readViewAndSettingsItem(referenceURL, filteredMetada[i], callback);
1022
- for (let i = 0; i < metadata.length; i++) {
1061
+ for (let i = 0; i < filteredMetada.length; i++) {
1023
1062
  readVersionOneRegionPath(targetRegion, referenceURL, filteredMetada[i], i, callback);
1024
1063
  }
1025
1064
  }