zincjs 2.4.1-beta.4 → 2.4.1-beta.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zincjs",
3
- "version": "2.4.1-beta.4",
3
+ "version": "2.4.1-beta.5",
4
4
  "description": "ZincJS (Web-based-Zinc-Visualisation)",
5
5
  "type": "module",
6
6
  "main": "build/zinc.js",
package/src/controls.js CHANGED
@@ -259,6 +259,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
259
259
  }
260
260
 
261
261
  this.onResize = () => {
262
+ hasUpdated = true;
262
263
  if (rect)
263
264
  rect = undefined;
264
265
  if (ndcControl)
@@ -355,6 +356,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
355
356
  }
356
357
 
357
358
  const translateViewport = translation => {
359
+ hasUpdated = true;
358
360
  _tempEye.copy(this.cameraObject.position).add(translation);
359
361
  if (checkTravelDistance()) {
360
362
  this.cameraObject.target.add(translation);
@@ -486,14 +488,17 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
486
488
  }
487
489
 
488
490
  const onDocumentTouchEnd = event => {
489
- const len = event.touches.length;
491
+ const previousState = this._state;
490
492
  this.touchZoomDistanceStart = this.touchZoomDistanceEnd = 0;
491
493
  this.targetTouchId = -1;
492
494
  this._state = STATE.NONE;
493
- if (len == 1) {
495
+ //A single finger tap, the lifted touch is in changedTouches
496
+ if (previousState === STATE.TOUCH_ROTATE && event.touches.length === 0 &&
497
+ event.changedTouches && event.changedTouches.length === 1 && rect) {
494
498
  if (zincRayCaster !== undefined) {
495
- if (this.pointer_x_start==(event.touches[0].clientX- rect.left) && this.pointer_y_start==(event.touches[0].clientY- rect.top)) {
496
- zincRayCaster.pick(this.cameraObject, event.touches[0].clientX, event.touches[0].clientY, this.renderer);
499
+ const touch = event.changedTouches[0];
500
+ if (this.pointer_x_start==(touch.clientX- rect.left) && this.pointer_y_start==(touch.clientY- rect.top)) {
501
+ zincRayCaster.pick(this, touch.clientX, touch.clientY, this.renderer);
497
502
  }
498
503
  }
499
504
  }
@@ -663,6 +668,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
663
668
  * @param {Number} Angle - Specify how much the camera shoudl rotate by.
664
669
  */
665
670
  this.rotateAboutLookAtpoint = (axis, angle) => {
671
+ hasUpdated = true;
666
672
  const returned_values = this.getVectorsFromRotateAboutLookAtPoints(axis, angle);
667
673
  this.cameraObject.position.copy(returned_values.position);
668
674
  this.updateDirectionalLight();
@@ -743,7 +749,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
743
749
  if (typeof this.cameraObject !== "undefined")
744
750
  {
745
751
  const height = rect.height;
746
- const a = this.cameraObject.position.clone();
752
+ const a = _v.copy(this.cameraObject.position);
747
753
  a.sub(this.cameraObject.target);
748
754
  const dist = a.length();
749
755
  const dy = 1.5 * delta_y/height;
@@ -786,6 +792,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
786
792
  * Force an update to the position of the directional light.
787
793
  */
788
794
  this.updateDirectionalLight = () => {
795
+ hasUpdated = true;
789
796
  if (this.directionalLight != 0) {
790
797
  this.directionalLight.position.set(this.cameraObject.position.x,
791
798
  this.cameraObject.position.y,
@@ -820,6 +827,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
820
827
  */
821
828
  this.disable = function () {
822
829
  enabled = false;
830
+ this._state = STATE.NONE;
823
831
  if (this.domElement && this.domElement.removeEventListener) {
824
832
  this.domElement.removeEventListener( 'mousedown', onDocumentMouseDown, false );
825
833
  this.domElement.removeEventListener( 'mousemove', onDocumentMouseMove, false );
@@ -984,6 +992,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
984
992
  * @param {Number} targetTimeFrame - bottom frame, top frame and the proportion.
985
993
  */
986
994
  this.setCurrentTimeFrame = targetTimeFrame => {
995
+ hasUpdated = true;
987
996
  if (numberOfCameraPoint > 2) {
988
997
  inbuildTime = duration * targetTimeFrame / (numberOfCameraPoint - 1);
989
998
  if (inbuildTime < 0.0)
@@ -1006,19 +1015,19 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
1006
1015
  const bottom_frame = time_frame[0];
1007
1016
  const top_frame = time_frame[1];
1008
1017
  const proportion = time_frame[2];
1009
- const bot_pos = [cameraPath[bottom_frame*3], cameraPath[bottom_frame*3+1], cameraPath[bottom_frame*3+2]];
1010
- const top_pos = [cameraPath[top_frame*3], cameraPath[top_frame*3+1], cameraPath[top_frame*3+2]];
1011
- const current_positions = [];
1012
- for (let i = 0; i < bot_pos.length; i++) {
1013
- current_positions.push(proportion * bot_pos[i] + (1.0 - proportion) * top_pos[i]);
1014
- }
1015
- this.cameraObject.position.set(current_positions[0], current_positions[1], current_positions[2]);
1016
- this.cameraObject.target.set(top_pos[0], top_pos[1], top_pos[2]);
1018
+ const bottom = bottom_frame * 3;
1019
+ const top = top_frame * 3;
1020
+ const x = proportion * cameraPath[bottom] + (1.0 - proportion) * cameraPath[top];
1021
+ const y = proportion * cameraPath[bottom + 1] + (1.0 - proportion) * cameraPath[top + 1];
1022
+ const z = proportion * cameraPath[bottom + 2] + (1.0 - proportion) * cameraPath[top + 2];
1023
+ this.cameraObject.position.set(x, y, z);
1024
+ this.cameraObject.target.set(cameraPath[top], cameraPath[top + 1], cameraPath[top + 2]);
1017
1025
  if (deviceOrientationControl)
1018
1026
  this.cameraObject.lookAt( this.cameraObject.target );
1019
1027
  if (updateLightWithPathFlag) {
1020
- this.directionalLight.position.set(current_positions[0], current_positions[1], current_positions[2]);
1021
- this.directionalLight.target.position.set(top_pos[0], top_pos[1], top_pos[2]);
1028
+ this.directionalLight.position.set(x, y, z);
1029
+ this.directionalLight.target.position.set(cameraPath[top], cameraPath[top + 1],
1030
+ cameraPath[top + 2]);
1022
1031
  }
1023
1032
  }
1024
1033
  }
@@ -1028,6 +1037,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
1028
1037
  * Force recalculation of the current path.
1029
1038
  */
1030
1039
  this.calculatePathNow = () => {
1040
+ hasUpdated = true;
1031
1041
  updatePath(0.0);
1032
1042
  }
1033
1043
 
@@ -1065,14 +1075,14 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
1065
1075
  } else if (currentMode === MODE.SMOOTH_CAMERA_TRANSITION && smoothCameraTransitionObject) {
1066
1076
  smoothCameraTransitionObject.update(delta);
1067
1077
  if (smoothCameraTransitionObject.isTransitionCompleted()) {
1068
- smoothCameraTransitionObject == undefined;
1078
+ smoothCameraTransitionObject = undefined;
1069
1079
  currentMode = MODE.DEFAULT;
1070
1080
  }
1071
1081
  controlEnabled = false;
1072
- } else if (currentMode === MODE.ROTATE_CAMERA_TRANSITION && rotateCameraTransitionObject) {
1082
+ } else if (currentMode === MODE.ROTATE_TRANSITION && rotateCameraTransitionObject) {
1073
1083
  rotateCameraTransitionObject.update(delta);
1074
1084
  if (rotateCameraTransitionObject.isTransitionCompleted()) {
1075
- rotateCameraTransitionObject == undefined;
1085
+ rotateCameraTransitionObject = undefined;
1076
1086
  currentMode = MODE.DEFAULT;
1077
1087
  }
1078
1088
  controlEnabled = false;
@@ -1183,6 +1193,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
1183
1193
  * Reset the viewport settings to the one provided by default viewport.
1184
1194
  */
1185
1195
  this.resetView = () => {
1196
+ hasUpdated = true;
1186
1197
  const viewport = viewports[defaultViewport];
1187
1198
  this.cameraObject.near = viewport.nearPlane;
1188
1199
  this.cameraObject.far = viewport.farPlane;
@@ -1354,7 +1365,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
1354
1365
  if (smoothCameraTransitionObject)
1355
1366
  currentMode = MODE.SMOOTH_CAMERA_TRANSITION;
1356
1367
  if (rotateCameraTransitionObject)
1357
- currentMode = MODE.ROTATE_CAMERA_TRANSITION;
1368
+ currentMode = MODE.ROTATE_TRANSITION;
1358
1369
  }
1359
1370
 
1360
1371
  /**
@@ -1379,7 +1390,7 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
1379
1390
  */
1380
1391
  this.isTransitioningCamera = () => {
1381
1392
  return (currentMode === MODE.SMOOTH_CAMERA_TRANSITION ||
1382
- currentMode === MODE.ROTATE_CAMERA_TRANSITION);
1393
+ currentMode === MODE.ROTATE_TRANSITION);
1383
1394
  }
1384
1395
 
1385
1396
  /**
@@ -1488,10 +1499,8 @@ const CameraControls = function ( object, domElement, renderer, scene ) {
1488
1499
  const SmoothCameraTransition = function(startingViewport, endingViewport, targetCameraIn, durationIn) {
1489
1500
  const startingEyePosition = startingViewport.eyePosition;
1490
1501
  const startingTargetPosition = startingViewport.targetPosition;
1491
- const startingUp = startingViewport.upVector;
1492
1502
  const endingEyePosition = endingViewport.eyePosition;
1493
1503
  const endingTargetPosition = endingViewport.targetPosition;
1494
- const endingUp = endingViewport.upVector;
1495
1504
  const targetCamera = targetCameraIn;
1496
1505
  let duration = durationIn;
1497
1506
  let inbuildTime = 0;
@@ -1516,17 +1525,15 @@ const SmoothCameraTransition = function(startingViewport, endingViewport, target
1516
1525
 
1517
1526
  const updateCameraSettings = () => {
1518
1527
  const ratio = inbuildTime / duration;
1519
- const eyePosition = [startingEyePosition[0] * (1.0 - ratio) + endingEyePosition[0] * ratio,
1520
- startingEyePosition[1] * (1.0 - ratio) + endingEyePosition[1] * ratio,
1521
- startingEyePosition[2] * (1.0 - ratio) + endingEyePosition[2] * ratio];
1522
- const targetPosition = [startingTargetPosition[0] * (1.0 - ratio) + endingTargetPosition[0] * ratio,
1523
- startingTargetPosition[1] * (1.0 - ratio) + endingTargetPosition[1] * ratio,
1524
- startingTargetPosition[2] * (1.0 - ratio) + endingTargetPosition[2] * ratio];
1525
- const upVector = [startingUp[0] * (1.0 - ratio) + endingUp[0] * ratio,
1526
- startingUp[1] * (1.0 - ratio) + endingUp[1] * ratio,
1527
- startingUp[2] * (1.0 - ratio) + endingUp[2] * ratio];
1528
- targetCamera.cameraObject.position.set( eyePosition[0], eyePosition[1], eyePosition[2]);
1529
- targetCamera.cameraObject.target.set( targetPosition[0], targetPosition[1], targetPosition[2] );
1528
+ const start = 1.0 - ratio;
1529
+ targetCamera.cameraObject.position.set(
1530
+ startingEyePosition[0] * start + endingEyePosition[0] * ratio,
1531
+ startingEyePosition[1] * start + endingEyePosition[1] * ratio,
1532
+ startingEyePosition[2] * start + endingEyePosition[2] * ratio);
1533
+ targetCamera.cameraObject.target.set(
1534
+ startingTargetPosition[0] * start + endingTargetPosition[0] * ratio,
1535
+ startingTargetPosition[1] * start + endingTargetPosition[1] * ratio,
1536
+ startingTargetPosition[2] * start + endingTargetPosition[2] * ratio);
1530
1537
  };
1531
1538
 
1532
1539
  this.update = delta => {
@@ -1609,6 +1616,7 @@ const RayCaster = function (sceneIn, hostSceneIn, callbackFunctionIn, hoverCallb
1609
1616
  let timeDiff = 0;
1610
1617
  let pickedObjects = new Array();
1611
1618
  let lastPosition = { zincCamera: undefined, x: -1 ,y: -1};
1619
+ let hoverFrameRequested = false;
1612
1620
  let pickableObjects = undefined;
1613
1621
 
1614
1622
  this.enable = () => {
@@ -1621,8 +1629,8 @@ const RayCaster = function (sceneIn, hostSceneIn, callbackFunctionIn, hoverCallb
1621
1629
 
1622
1630
  this.getIntersectsObject = (zincCamera) => {
1623
1631
  if (hostScene !== scene) {
1624
- const threejsScene = scene.getThreeJSScene();
1625
- renderer.render(threejsScene, zincCamera.cameraObject);
1632
+ //Only the world matrices need to be up to date for raycasting
1633
+ scene.getThreeJSScene().updateMatrixWorld();
1626
1634
  }
1627
1635
  let objects = pickableObjects ? pickableObjects : scene.getPickableThreeJSObjects();
1628
1636
  //Reset pickedObjects array
@@ -1669,6 +1677,8 @@ const RayCaster = function (sceneIn, hostSceneIn, callbackFunctionIn, hoverCallb
1669
1677
  }
1670
1678
  }
1671
1679
  callbackFunction(pickedObjects, x, y);
1680
+ //The callback may have changed what is displayed
1681
+ hostScene?.invalidate?.();
1672
1682
  }
1673
1683
  }
1674
1684
 
@@ -1685,24 +1695,36 @@ const RayCaster = function (sceneIn, hostSceneIn, callbackFunctionIn, hoverCallb
1685
1695
  lastHoveredEmpty = false;
1686
1696
  }
1687
1697
  hoverCallbackFunction(pickedObjects, x, y);
1698
+ //The callback may have changed what is displayed
1699
+ hostScene?.invalidate?.();
1688
1700
  }
1689
1701
  }
1690
1702
 
1691
1703
  this.move = (zincCamera, x, y) => {
1692
1704
  if (enabled && renderer && scene && zincCamera && hoverCallbackFunction) {
1705
+ lastPosition.zincCamera = zincCamera;
1706
+ lastPosition.x = x;
1707
+ lastPosition.y = y;
1693
1708
  if (scene.displayMarkers) {
1694
- hovered(zincCamera, x, y);
1709
+ //Markers need responsive hovering, but there is no need to
1710
+ //raycast more than once per frame.
1711
+ if (typeof requestAnimationFrame !== 'function') {
1712
+ hovered(zincCamera, x, y);
1713
+ } else if (!hoverFrameRequested) {
1714
+ hoverFrameRequested = true;
1715
+ requestAnimationFrame(() => {
1716
+ hoverFrameRequested = false;
1717
+ hovered(lastPosition.zincCamera, lastPosition.x, lastPosition.y);
1718
+ });
1719
+ }
1695
1720
  } else {
1696
- lastPosition.zincCamera = zincCamera;
1697
- lastPosition.x = x;
1698
- lastPosition.y = y;
1699
1721
  if (!awaiting) {
1700
1722
  timeDiff = lastHoveredDate ? Date.now() - lastHoveredDate.getTime() : 250;
1701
1723
  if (timeDiff >= 250) {
1702
1724
  hovered(zincCamera, x, y);
1703
1725
  } else {
1704
1726
  awaiting = true;
1705
- setTimeout(awaitMove(lastPosition), timeDiff);
1727
+ setTimeout(awaitMove(lastPosition), 250 - timeDiff);
1706
1728
  }
1707
1729
  }
1708
1730
  }
@@ -1825,7 +1847,7 @@ Object.assign( StereoCameraZoomFixed.prototype, {
1825
1847
  const projectionMatrix = camera.projectionMatrix.clone();
1826
1848
  const eyeSep = 0.064 / 2;
1827
1849
  const eyeSepOnProjection = eyeSep * near / focus;
1828
- const ymax = near * Math.tan( THREE.Math.DEG2RAD * fov * 0.5 ) / camera.zoom;
1850
+ const ymax = near * Math.tan( THREE.MathUtils.DEG2RAD * fov * 0.5 ) / camera.zoom;
1829
1851
  let xmin, xmax;
1830
1852
 
1831
1853
  // translate xOffset
@@ -1953,16 +1975,16 @@ const ModifiedDeviceOrientationControls = function ( object ) {
1953
1975
 
1954
1976
  const q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
1955
1977
 
1956
- return (cameraObject, alpha, beta, gamma, orient) => {
1978
+ const vector = new THREE.Vector3();
1979
+
1980
+ const quaternion = new THREE.Quaternion();
1957
1981
 
1958
- const vector = new THREE.Vector3(0, 0, 1);
1982
+ return (cameraObject, alpha, beta, gamma, orient) => {
1959
1983
 
1960
1984
  vector.subVectors(cameraObject.target, cameraObject.position);
1961
1985
 
1962
1986
  euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
1963
1987
 
1964
- const quaternion = new THREE.Quaternion();
1965
-
1966
1988
  quaternion.setFromEuler( euler ); // orient the device
1967
1989
 
1968
1990
  quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
@@ -2003,10 +2025,10 @@ const ModifiedDeviceOrientationControls = function ( object ) {
2003
2025
 
2004
2026
  if ( scope.enabled === false ) return;
2005
2027
 
2006
- const alpha = scope.deviceOrientation.alpha ? THREE.Math.degToRad( scope.deviceOrientation.alpha ) : 0; // Z
2007
- const beta = scope.deviceOrientation.beta ? THREE.Math.degToRad( scope.deviceOrientation.beta ) : 0; // X'
2008
- const gamma = scope.deviceOrientation.gamma ? THREE.Math.degToRad( scope.deviceOrientation.gamma ) : 0; // Y''
2009
- const orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O
2028
+ const alpha = scope.deviceOrientation.alpha ? THREE.MathUtils.degToRad( scope.deviceOrientation.alpha ) : 0; // Z
2029
+ const beta = scope.deviceOrientation.beta ? THREE.MathUtils.degToRad( scope.deviceOrientation.beta ) : 0; // X'
2030
+ const gamma = scope.deviceOrientation.gamma ? THREE.MathUtils.degToRad( scope.deviceOrientation.gamma ) : 0; // Y''
2031
+ const orient = scope.screenOrientation ? THREE.MathUtils.degToRad( scope.screenOrientation ) : 0; // O
2010
2032
 
2011
2033
  setObjectQuaternion( scope.object, alpha, beta, gamma, orient );
2012
2034
 
@@ -6,8 +6,11 @@ const FileLoader = THREE.FileLoader;
6
6
  const mergeGlyphData = (glyphData) => {
7
7
  const merge = (glyphData1, glyphData2) => {
8
8
  glyphData1.metadata.number_of_vertices += glyphData2.metadata.number_of_vertices;
9
- if ('labels' in glyphData1) {
10
- glyphData1.labels.push(...glyphData1.labels);
9
+ if ('labels' in glyphData1 && glyphData2.labels) {
10
+ const labels = glyphData2.labels;
11
+ for (let i = 0; i < labels.length; i++) {
12
+ glyphData1.labels.push(labels[i]);
13
+ }
11
14
  }
12
15
  const fields = ['axis1', 'axis2', 'axis3', 'colors', 'positions', 'scale'];
13
16
  fields.forEach(field => {
@@ -42,12 +45,13 @@ const mergeGeometries = (geometries) => {
42
45
  return undefined;
43
46
  }
44
47
 
45
- const IndexedSourcesHandler = function(urlIn, crossOrigin, onDownloadedCallback) {
48
+ const IndexedSourcesHandler = function(urlIn, crossOrigin, onDownloadedCallback, onErrorCallback) {
46
49
  const fileLoader = new FileLoader();
47
50
  const jsonLoader = new JSONLoader();
48
51
  fileLoader.crossOrigin = crossOrigin;
49
52
  const url = urlIn;
50
53
  const onDownloaded = onDownloadedCallback;
54
+ const onDownloadError = onErrorCallback;
51
55
  let data = undefined;
52
56
  let downloading = false;
53
57
  let finished = false;
@@ -100,6 +104,9 @@ const IndexedSourcesHandler = function(urlIn, crossOrigin, onDownloadedCallback)
100
104
  items.forEach((item) => {
101
105
  processItemError(item);
102
106
  });
107
+ if (onDownloadError) {
108
+ onDownloadError(xhr);
109
+ }
103
110
  }
104
111
  }
105
112
 
@@ -132,32 +139,62 @@ const IndexedSourcesHandler = function(urlIn, crossOrigin, onDownloadedCallback)
132
139
  } else {
133
140
  items.push(item);
134
141
  downloading = true;
135
- fileLoader.load(url, onDownloaded, progressHandling, errorHandling);
142
+ fileLoader.load(url, onDownloaded, progressHandling(), errorHandling());
136
143
  }
137
144
  }
138
145
  }
139
146
 
140
- const MultiSourcesHandler = function(numberIn, onLoadCallback, options) {
147
+ const MultiSourcesHandler = function(numberIn, onLoadCallback, onErrorCallback, options) {
141
148
  const allData = [];
142
149
  const number = numberIn;
143
150
  const onLoad = onLoadCallback;
151
+ const onError = onErrorCallback;
144
152
  let totalDownloaded = 0;
145
- const isGlyphData = options.isGlyphsets;
153
+ let failure = undefined;
154
+ const isGlyphData = options?.isGlyphsets;
155
+
156
+ //Release whatever was downloaded successfully and report the first failure
157
+ const reportFailure = () => {
158
+ if (!isGlyphData) {
159
+ allData.forEach((data) => {
160
+ if (data) {
161
+ data[0]?.dispose?.();
162
+ data[1]?.forEach((material) => material.dispose());
163
+ }
164
+ });
165
+ }
166
+ if (onError) {
167
+ onError(...failure);
168
+ }
169
+ }
170
+
171
+ this.itemFailed = (order, args) => {
172
+ if (!failure) {
173
+ failure = args;
174
+ }
175
+ totalDownloaded++;
176
+ if (totalDownloaded == number) {
177
+ reportFailure();
178
+ }
179
+ }
146
180
 
147
181
  this.itemDownloaded = (order, args) => {
148
182
  allData[order]= args;
149
183
  totalDownloaded++;
150
184
  if (totalDownloaded == number) {
151
- if (allData.length > 0) {
185
+ if (failure) {
186
+ reportFailure();
187
+ } else if (allData.length > 0) {
152
188
  //Assume when item length is one then it is a glyphset otherwise geometry
153
189
  if (!isGlyphData) {
154
190
  const materials = allData[0][1];
155
191
  const geometries = allData.map((data) => data[0]);
156
192
  //All geometries will be merged into the first one
157
193
  const geometry = mergeGeometries(geometries);
194
+ //mergeGeometries has disposed the source geometries,
195
+ //only the first set of materials is kept
158
196
  for (let i = 1; i < number; i++) {
159
- allData[order][0].dispose();
160
- allData[order][1].forEach((material) => material.dispose());
197
+ allData[i][1]?.forEach((material) => material.dispose());
161
198
  }
162
199
  onLoad(geometry, materials);
163
200
  } else {
@@ -186,7 +223,7 @@ const PrimitivesLoader = function () {
186
223
  //Load the first file then the rest will be handled separately
187
224
  const loadFromMultipleSources = (urls, onLoad, onProgress, onError, options) => {
188
225
  const number = urls.length;
189
- const msHandler = new MultiSourcesHandler(number, onLoad, options);
226
+ const msHandler = new MultiSourcesHandler(number, onLoad, onError, options);
190
227
  //The order here will give us hint on the sequence on merging the primitives
191
228
  let order = 0;
192
229
  urls.forEach((url) => {
@@ -204,8 +241,10 @@ const PrimitivesLoader = function () {
204
241
  if (!indexedLoader) {
205
242
  if (MAX_DOWNLOAD > concurrentDownloads) {
206
243
  const onLoadCallback = new onFinally(undefined, this, newOptions);
244
+ const onErrorCallback = new onFinally(undefined, this, {});
207
245
  ++concurrentDownloads;
208
- indexedLoader = new IndexedSourcesHandler(url, this.crossOrigin, onLoadCallback);
246
+ indexedLoader = new IndexedSourcesHandler(url, this.crossOrigin, onLoadCallback,
247
+ onErrorCallback);
209
248
  indexedLoaders[url] = indexedLoader;
210
249
  } else {
211
250
  waitingList.push({
@@ -231,8 +270,8 @@ const PrimitivesLoader = function () {
231
270
  if (MAX_DOWNLOAD > concurrentDownloads) {
232
271
  ++concurrentDownloads;
233
272
  const onLoadCallback = new onFinally(onLoad, this, options);
234
- const onErrorCallback = new onFinally(onError, this, options);
235
- if (!options.isGlyphsets) {
273
+ const onErrorCallback = new onFinally(onError, this, options, true);
274
+ if (!options?.isGlyphsets) {
236
275
  jsonloader.crossOrigin = this.crossOrigin;
237
276
  jsonloader.load(url, onLoadCallback, onProgress, onErrorCallback);
238
277
  } else {
@@ -279,11 +318,15 @@ const PrimitivesLoader = function () {
279
318
  }
280
319
  }
281
320
 
282
- const onFinally = function(callback, loader, options) {
321
+ const onFinally = function(callback, loader, options, isError = false) {
283
322
  return (...args) => {
284
323
  --concurrentDownloads;
285
324
  if (options?.msHandler) {
286
- options.msHandler.itemDownloaded(options.order, args);
325
+ if (isError) {
326
+ options.msHandler.itemFailed(options.order, args);
327
+ } else {
328
+ options.msHandler.itemDownloaded(options.order, args);
329
+ }
287
330
  } else if (options?.isHandler) {
288
331
  options.isHandler.downloadCompleted(args);
289
332
  } else {
@@ -297,7 +340,7 @@ const PrimitivesLoader = function () {
297
340
  }
298
341
 
299
342
  this.parse = data => {
300
- return loader.parse(data);
343
+ return jsonloader.parse(data);
301
344
  }
302
345
 
303
346
  }
package/src/minimap.js CHANGED
@@ -33,12 +33,34 @@ const Minimap = function (sceneIn) {
33
33
  this.mask = new THREE.Mesh( geometry, material );
34
34
  let _box = new THREE.Box3();
35
35
  let _center = new THREE.Vector3();
36
+ const _coord = new THREE.Vector3();
37
+ const _lookAt = new THREE.Vector3();
38
+ const _target = new THREE.Vector3();
39
+ const _corners = [new THREE.Vector3(), new THREE.Vector3(),
40
+ new THREE.Vector3(), new THREE.Vector3()];
41
+ const _boundaryVertices = [_corners[0], _corners[1], _corners[2],
42
+ _corners[2], _corners[3], _corners[0]];
43
+ //State the minimap was last computed from, it only needs to be updated
44
+ //when the scene or the main camera changes.
45
+ const _lastBoundingBox = new THREE.Box3();
46
+ const _lastMatrixWorld = new THREE.Matrix4();
47
+ const _lastProjectionMatrix = new THREE.Matrix4();
48
+ let _lastNear = undefined;
49
+ let _cameraUpdated = false;
50
+ //Matrix4.equals treats NaN as different, e.g. with a zero sized canvas
51
+ const matrixEquals = (a, b) => {
52
+ for (let i = 0; i < 16; i++) {
53
+ if (!Object.is(a.elements[i], b.elements[i]))
54
+ return false;
55
+ }
56
+ return true;
57
+ }
36
58
 
37
59
  this.getDiffFromNormalised = (x, y) => {
38
60
  _box.setFromBufferAttribute(positionAttributes).getCenter(_center);
39
- let coord = _center.clone().project(this.camera);
40
- let new_coord = new THREE.Vector3(x, y, coord.z).unproject(this.camera);
41
- return new_coord.sub(_center);
61
+ _coord.copy(_center).project(this.camera);
62
+ //Returns a new vector, the caller may keep it
63
+ return new THREE.Vector3(x, y, _coord.z).unproject(this.camera).sub(_center);
42
64
  }
43
65
 
44
66
  let setCurrentCameraSettings = (diameter, newViewport) => {
@@ -53,28 +75,41 @@ const Minimap = function (sceneIn) {
53
75
  this.camera.up.set(newViewport.upVector[0], newViewport.upVector[1],
54
76
  newViewport.upVector[2]);
55
77
  if (newViewport.targetPosition)
56
- this.camera.lookAt(new THREE.Vector3(newViewport.targetPosition[0],
78
+ this.camera.lookAt(_lookAt.set(newViewport.targetPosition[0],
57
79
  newViewport.targetPosition[1], newViewport.targetPosition[2]));
58
80
  this.camera.zoom = 1 / diameter;
59
81
  this.camera.updateProjectionMatrix();
60
82
  }
61
83
 
62
84
  this.getBoundary = () => {
63
- let target = new THREE.Vector3().copy(
64
- targetScene.camera.target).project(targetScene.camera);
65
- let v1 = new THREE.Vector3(-1, -1, target.z).unproject(targetScene.camera);
66
- let v2 = new THREE.Vector3(1, -1, target.z).unproject(targetScene.camera);
67
- let v3 = new THREE.Vector3(1, 1, target.z).unproject(targetScene.camera);
68
- let v4 = new THREE.Vector3(-1, 1, target.z).unproject(targetScene.camera);
69
- let array = [v1, v2, v3, v3, v4, v1];
70
- copyVector3sArray(positionAttributes, array);
85
+ const camera = targetScene.camera;
86
+ _target.copy(camera.target).project(camera);
87
+ _corners[0].set(-1, -1, _target.z).unproject(camera);
88
+ _corners[1].set(1, -1, _target.z).unproject(camera);
89
+ _corners[2].set(1, 1, _target.z).unproject(camera);
90
+ _corners[3].set(-1, 1, _target.z).unproject(camera);
91
+ copyVector3sArray(positionAttributes, _boundaryVertices);
71
92
  }
72
93
 
73
94
  this.updateCamera = () => {
95
+ const camera = targetScene.camera;
96
+ let boundingBox = targetScene.getBoundingBox();
97
+ //Nothing has changed since the last update
98
+ if (_cameraUpdated && boundingBox && boundingBox.equals(_lastBoundingBox) &&
99
+ _lastNear === camera.near &&
100
+ matrixEquals(_lastMatrixWorld, camera.matrixWorld) &&
101
+ matrixEquals(_lastProjectionMatrix, camera.projectionMatrix)) {
102
+ return;
103
+ }
74
104
  this.getBoundary();
105
+ _lastMatrixWorld.copy(camera.matrixWorld);
106
+ _lastProjectionMatrix.copy(camera.projectionMatrix);
107
+ _lastNear = camera.near;
108
+ _cameraUpdated = false;
75
109
  let cameraControl = targetScene.getZincCameraControls();
76
- let boundingBox = targetScene.getBoundingBox();
77
110
  if (boundingBox) {
111
+ _lastBoundingBox.copy(boundingBox);
112
+ _cameraUpdated = true;
78
113
  // enlarge radius to keep image within edge of window
79
114
  const diameter = boundingBox.min.distanceTo(boundingBox.max);
80
115
  const radius = diameter / 2.0;
@@ -99,6 +99,7 @@ const Geometry = function () {
99
99
  * @param {Boolean} wireframe - Flag to turn on/off wireframe display.
100
100
  */
101
101
  this.setWireframe = wireframe => {
102
+ this.requestRender();
102
103
  this.morph.material.wireframe = wireframe;
103
104
  }
104
105
 
@@ -106,6 +107,7 @@ const Geometry = function () {
106
107
  * Edit Vertice in index.
107
108
  */
108
109
  this.editVertices = function(coords, i) {
110
+ this.requestRender();
109
111
  if (coords && coords.length) {
110
112
  let mesh = this.getMorph();
111
113
  const attribute = mesh.geometry.getAttribute("position");
@@ -63,6 +63,7 @@ const Glyph = function (geometry, materialIn, idIn, glyphsetIn) {
63
63
  * @param {String} text - Label to be set for this instance
64
64
  */
65
65
  this.setLabel = text => {
66
+ parent?.requestRender?.();
66
67
  if (text && (typeof text === 'string' || text instanceof String)) {
67
68
  labelString = text;
68
69
  if (this.morph)
@@ -78,6 +79,7 @@ const Glyph = function (geometry, materialIn, idIn, glyphsetIn) {
78
79
  * getColour()) - e.g. genuinely no colour data at all for this glyphset.
79
80
  */
80
81
  this.showLabel = (colour) => {
82
+ parent?.requestRender?.();
81
83
  if (label) {
82
84
  _position = label.getPosition();
83
85
  this.group.remove(label.getSprite());
@@ -88,7 +90,8 @@ const Glyph = function (geometry, materialIn, idIn, glyphsetIn) {
88
90
  label = new Label(labelString, _colour || colour);
89
91
  label.setPosition(_position[0], _position[1], _position[2]);
90
92
  const sprite = label.getSprite();
91
- sprite.material.alphaTest = 0.5;
93
+ //Only discard fully transparent pixels to keep the smooth edges
94
+ sprite.material.alphaTest = 0.05;
92
95
  sprite.material.transparent = true;
93
96
  sprite.material.depthWrite = false;
94
97
  this.group.add(label.getSprite());
@@ -99,6 +102,7 @@ const Glyph = function (geometry, materialIn, idIn, glyphsetIn) {
99
102
  * Hide label with the choosen colour.
100
103
  */
101
104
  this.hideLabel = () => {
105
+ parent?.requestRender?.();
102
106
  if (label) {
103
107
  _position = label.getPosition();
104
108
  this.group.remove(label.getSprite());
@@ -135,6 +139,7 @@ const Glyph = function (geometry, materialIn, idIn, glyphsetIn) {
135
139
  * transformation.
136
140
  */
137
141
  this.setTransformation = (position, axis1, axis2, axis3) => {
142
+ parent?.requestRender?.();
138
143
  if (this.morph) {
139
144
  this.morph.matrix.elements[0] = axis1[0];
140
145
  this.morph.matrix.elements[1] = axis1[1];
@@ -170,6 +175,7 @@ const Glyph = function (geometry, materialIn, idIn, glyphsetIn) {
170
175
  * @param {THREE.Color} color - Colour to be set.
171
176
  */
172
177
  this.setColour = (color) => {
178
+ parent?.requestRender?.();
173
179
  _colour = color ? color.clone() : undefined;
174
180
  if (label)
175
181
  label.setColour(color);
@@ -190,6 +196,10 @@ const Glyph = function (geometry, materialIn, idIn, glyphsetIn) {
190
196
  * Clear and free its memory.
191
197
  */
192
198
  this.dispose = () => {
199
+ if (label) {
200
+ label.dispose();
201
+ label = undefined;
202
+ }
193
203
  if (this.material)
194
204
  this.material.dispose();
195
205
  this.morph = undefined;