zincjs 1.0.13 → 1.0.15

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 (45) hide show
  1. package/build/zinc.frontend.js +1 -1
  2. package/build/zinc.js +43 -35
  3. package/build/zinc.js.map +1 -1
  4. package/package.json +3 -3
  5. package/src/assets/disc.png +0 -0
  6. package/src/assets/mapMarker.svg +11 -0
  7. package/src/controls.js +1594 -0
  8. package/src/geometryCSG.js +148 -0
  9. package/src/glyphsetCSG.js +84 -0
  10. package/src/loaders/GLTFToZincJSLoader.js +85 -0
  11. package/src/loaders/JSONLoader.js +697 -0
  12. package/src/loaders/OBJLoader.js +911 -0
  13. package/src/loaders/STLLoader.js +399 -0
  14. package/src/loaders/primitivesLoader.js +46 -0
  15. package/src/minimap.js +82 -0
  16. package/src/primitives/augmentShader.js +22 -0
  17. package/src/primitives/geometry.js +109 -0
  18. package/src/primitives/glyph.js +150 -0
  19. package/src/primitives/glyphset.js +657 -0
  20. package/src/primitives/label.js +51 -0
  21. package/src/primitives/lines.js +35 -0
  22. package/src/primitives/marker.js +88 -0
  23. package/src/primitives/pointset.js +53 -0
  24. package/src/primitives/texturePrimitive.js +16 -0
  25. package/src/primitives/textureSlides.js +118 -0
  26. package/src/primitives/zincObject.js +573 -0
  27. package/src/region.js +554 -0
  28. package/src/renderer.js +612 -0
  29. package/src/scene.js +963 -0
  30. package/src/sceneExporter.js +32 -0
  31. package/src/sceneLoader.js +842 -0
  32. package/src/texture/texture.js +57 -0
  33. package/src/texture/textureArray.js +85 -0
  34. package/src/three/GLTFExporter.js +2448 -0
  35. package/src/three/Geometry.js +2084 -0
  36. package/src/three/Loader.js +344 -0
  37. package/src/three/Points.js +223 -0
  38. package/src/three/line/Line.js +293 -0
  39. package/src/three/line/LineSegments.js +65 -0
  40. package/src/three-js-csg.js +564 -0
  41. package/src/utilities.js +321 -0
  42. package/src/videoHandler.js +92 -0
  43. package/src/workers/geometryCSG.worker.js +73 -0
  44. package/src/workers/geometryCSGInternal.js +58 -0
  45. package/src/zinc.js +38 -0
@@ -0,0 +1,842 @@
1
+ const THREE = require('three');
2
+ const resolveURL = require('./utilities').resolveURL;
3
+ const STLLoader = require('./loaders/STLLoader').STLLoader;
4
+ const OBJLoader = require('./loaders/OBJLoader').OBJLoader;
5
+ const PrimitivesLoader = require('./loaders/primitivesLoader').PrimitivesLoader;
6
+
7
+ const createNewURL = (target, reference) => {
8
+ let newURL = (new URL(target, reference)).href;
9
+ //Make sure the target url does not contain parameters
10
+ if (target && target.split("?").length < 2) {
11
+ const paramsStrings = reference.split("?");
12
+ //There are parameters, add them to the target
13
+ if (paramsStrings.length === 2) {
14
+ newURL = newURL + "?" + paramsStrings[1];
15
+ }
16
+ }
17
+ return newURL;
18
+ }
19
+
20
+
21
+ exports.SceneLoader = function (sceneIn) {
22
+ const scene = sceneIn;
23
+ this.toBeDownloaded = 0;
24
+ this.progressMap = [];
25
+ let viewLoaded = false;
26
+ let errorDownload = false;
27
+ const primitivesLoader = new PrimitivesLoader();
28
+ /**
29
+ * This function returns a three component array, which contains
30
+ * [totalsize, totalLoaded and errorDownload] of all the downloads happening
31
+ * in this scene.
32
+ * @returns {Array}
33
+ */
34
+ this.getDownloadProgress = () => {
35
+ let totalSize = 0;
36
+ let totalLoaded = 0;
37
+ let unknownFound = false;
38
+
39
+ for (const key in this.progressMap) {
40
+ const progress = this.progressMap[key];
41
+
42
+ totalSize += progress[1];
43
+ totalLoaded += progress[0];
44
+
45
+ if (progress[1] == 0)
46
+ unknownFound = true;
47
+ }
48
+ if (unknownFound) {
49
+ totalSize = 0;
50
+ }
51
+ return [ totalSize, totalLoaded, errorDownload ];
52
+ }
53
+
54
+ //Stores the current progress of downloads
55
+ this.onProgress = id => {
56
+ return xhr => {
57
+ this.progressMap[id] = [ xhr.loaded, xhr.total ];
58
+ };
59
+ }
60
+
61
+ this.onError = finishCallback => {
62
+ return xhr => {
63
+ this.toBeDownloaded = this.toBeDownloaded - 1;
64
+ errorDownload = true;
65
+ if (finishCallback) {
66
+ finishCallback();
67
+ }
68
+ }
69
+ };
70
+
71
+ let loadMultipleViews = (referenceURL, views) => {
72
+ const defaultView = views.Default;
73
+ if (views.Inline) {
74
+ scene.setupMultipleViews(defaultView, views.Entries);
75
+ } else {
76
+ const promises = [];
77
+ for (const [key, value] of Object.entries(views.Entries)) {
78
+ if (referenceURL) {
79
+ newURL = createNewURL(value, referenceURL);
80
+ promises.push(new Promise((resolve, reject) => {
81
+ // Add parameters if we are sent them
82
+ fetch(newURL)
83
+ .then(response => response.json())
84
+ .then(data => resolve({key: key, data: data}))
85
+ .catch(data => reject(data));
86
+ }));
87
+ }
88
+ }
89
+ Promise.all(promises)
90
+ .then(values => {
91
+ const entries = {};
92
+ values.forEach(entry => {
93
+ entries[entry.key] = entry.data;
94
+ });
95
+ scene.setupMultipleViews(defaultView, entries);
96
+ let zincCameraControls = scene.getZincCameraControls();
97
+ if (zincCameraControls)
98
+ zincCameraControls.setCurrentViewport(defaultView);
99
+ viewLoaded = true;
100
+ });
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Load the viewport from an external location provided by the url.
106
+ * @param {String} URL - address to the file containing viewport information.
107
+ */
108
+ this.loadViewURL = (url, finishCallback) => {
109
+ this.toBeDownloaded += 1;
110
+ const xmlhttp = new XMLHttpRequest();
111
+ xmlhttp.onreadystatechange = () => {
112
+ if (xmlhttp.readyState == 4) {
113
+ if(xmlhttp.status == 200) {
114
+ const viewData = JSON.parse(xmlhttp.responseText);
115
+ scene.setupMultipleViews("default", { "default" : viewData });
116
+ scene.resetView();
117
+ viewLoaded = true;
118
+ --this.toBeDownloaded;
119
+ if (finishCallback != undefined && (typeof finishCallback == 'function'))
120
+ finishCallback();
121
+ } else {
122
+ this.onError();
123
+ }
124
+ }
125
+ }
126
+ requestURL = resolveURL(url);
127
+ xmlhttp.open("GET", requestURL, true);
128
+ xmlhttp.send();
129
+ }
130
+
131
+ /**
132
+ * Load a legacy model(s) format with the provided URLs and parameters. This only loads the geometry
133
+ * without any of the metadata. Therefore, extra parameters should be provided.
134
+ *
135
+ * @deprecated
136
+ */
137
+ this.loadModelsURL = (region, urls, colours, opacities, timeEnabled, morphColour, finishCallback) => {
138
+ const number = urls.length;
139
+ this.toBeDownloaded += number;
140
+ for (let i = 0; i < number; i++) {
141
+ const filename = urls[i];
142
+ let colour = require('./zinc').defaultMaterialColor;
143
+ let opacity = require('./zinc').defaultOpacity;
144
+ if (colours != undefined && colours[i] != undefined)
145
+ colour = colours[i] ? true : false;
146
+ if (opacities != undefined && opacities[i] != undefined)
147
+ opacity = opacities[i];
148
+ let localTimeEnabled = 0;
149
+ if (timeEnabled != undefined && timeEnabled[i] != undefined)
150
+ localTimeEnabled = timeEnabled[i] ? true : false;
151
+ let localMorphColour = 0;
152
+ if (morphColour != undefined && morphColour[i] != undefined)
153
+ localMorphColour = morphColour[i] ? true : false;
154
+ primitivesLoader.load(resolveURL(filename), meshloader(region, colour, opacity, localTimeEnabled, localMorphColour, undefined, undefined,
155
+ undefined, finishCallback), this.onProgress(i), this.onError(finishCallback));
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Load a legacy file format containing the viewport and its meta file from an external
161
+ * location provided by the url. Use the new metadata format with
162
+ * {@link Zinc.SceneLoader.#loadMetadataURL} instead.
163
+ *
164
+ * @param {String} URL - address to the file containing viewport and model information.
165
+ * @deprecated
166
+ */
167
+ this.loadFromViewURL = (targetRegion, jsonFilePrefix, finishCallback) => {
168
+ const xmlhttp = new XMLHttpRequest();
169
+ xmlhttp.onreadystatechange = () => {
170
+ if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
171
+ const viewData = JSON.parse(xmlhttp.responseText);
172
+ scene.loadView(viewData);
173
+ const urls = [];
174
+ const filename_prefix = jsonFilePrefix + "_";
175
+ for (let i = 0; i < viewData.numberOfResources; i++) {
176
+ const filename = filename_prefix + (i + 1) + ".json";
177
+ urls.push(filename);
178
+ }
179
+ this.loadModelsURL(targetRegion, urls, viewData.colour, viewData.opacity, viewData.timeEnabled, viewData.morphColour, finishCallback);
180
+ }
181
+ }
182
+ requestURL = resolveURL(jsonFilePrefix + "_view.json");
183
+ xmlhttp.open("GET", requestURL, true);
184
+ xmlhttp.send();
185
+ }
186
+
187
+ //Internal loader for a regular zinc geometry.
188
+ const linesloader = (region, localTimeEnabled, localMorphColour, groupName, anatomicalId, renderOrder, finishCallback) => {
189
+ return (geometry, materials) => {
190
+ const newLines = new (require('./primitives/lines').Lines)();
191
+ let material = undefined;
192
+ if (materials && materials[0]) {
193
+ material = new THREE.LineBasicMaterial({color:materials[0].color.clone()});
194
+ if (1.0 > materials[0].opacity) {
195
+ material.transparent = true;
196
+ }
197
+ material.opacity = materials[0].opacity;
198
+ material.morphTargets = localTimeEnabled;
199
+ material.vertexColors = materials[0].vertexColors;
200
+ }
201
+ let options = {};
202
+ options.localTimeEnabled = localTimeEnabled;
203
+ options.localMorphColour = localMorphColour;
204
+
205
+ if (newLines) {
206
+ newLines.createLineSegment(geometry, material, options);
207
+ newLines.setName(groupName);
208
+ newLines.anatomicalId = anatomicalId;
209
+ newLines.setRenderOrder = renderOrder;
210
+ region.addZincObject(newLines);
211
+ newLines.setDuration(scene.getDuration());
212
+ }
213
+ --this.toBeDownloaded;
214
+ geometry.dispose();
215
+ if (finishCallback != undefined && (typeof finishCallback == 'function'))
216
+ finishCallback(newLines);
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Load lines into this scene object.
222
+ *
223
+ * @param {String} metaurl - Provide informations such as transformations, colours
224
+ * and others for each of the glyph in the glyphsset.
225
+ * @param {Boolean} timeEnabled - Indicate if morphing is enabled.
226
+ * @param {Boolean} morphColour - Indicate if color morphing is enabled.
227
+ * @param {STRING} groupName - name to assign the pointset's groupname to.
228
+ * @param {Function} finishCallback - Callback function which will be called
229
+ * once the glyphset is succssfully load in.
230
+ */
231
+ this.loadLinesURL = (region, url, timeEnabled, morphColour, groupName, finishCallback, options) => {
232
+ let localTimeEnabled = 0;
233
+ this.toBeDownloaded += 1;
234
+ let isInline = (options && options.isInline) ? options.isInline : false;
235
+ let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
236
+ let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
237
+ if (timeEnabled != undefined)
238
+ localTimeEnabled = timeEnabled ? true : false;
239
+ let localMorphColour = 0;
240
+ if (morphColour != undefined)
241
+ localMorphColour = morphColour ? true : false;
242
+ if (isInline) {
243
+ var object = primitivesLoader.parse( url );
244
+ (linesloader(region, localTimeEnabled, localMorphColour, groupName, anatomicalId,
245
+ renderOrder, finishCallback))( object.geometry, object.materials );
246
+ } else {
247
+ primitivesLoader.load(url, linesloader(region, localTimeEnabled, localMorphColour, groupName,
248
+ anatomicalId, renderOrder, finishCallback), this.onProgress(i), this.onError(finishCallback));
249
+ }
250
+ }
251
+
252
+ const loadGlyphset = (region, glyphsetData, glyphurl, groupName, finishCallback, options) => {
253
+ let isInline = (options && options.isInline) ? options.isInline : undefined;
254
+ let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
255
+ let displayLabels = (options && options.displayLabels) ? options.displayLabels : undefined;
256
+ let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
257
+ const newGlyphset = new (require('./primitives/glyphset').Glyphset)();
258
+ newGlyphset.setDuration(scene.getDuration());
259
+ newGlyphset.groupName = groupName;
260
+ let myCallback = () => {
261
+ --this.toBeDownloaded;
262
+ if (finishCallback != undefined && (typeof finishCallback == 'function'))
263
+ finishCallback(newGlyphset);
264
+ }
265
+ ++this.toBeDownloaded;
266
+ if (isInline) {
267
+ newGlyphset.load(glyphsetData, glyphurl, myCallback, isInline, displayLabels);
268
+ }
269
+ else {
270
+ newGlyphset.load(glyphsetData, resolveURL(glyphurl), myCallback, isInline, displayLabels);
271
+ }
272
+ newGlyphset.anatomicalId = anatomicalId;
273
+ newGlyphset.setRenderOrder(renderOrder);
274
+ region.addZincObject(newGlyphset);
275
+ };
276
+
277
+ //Load a glyphset into this scene.
278
+ const onLoadGlyphsetReady = (region, xmlhttp, glyphurl, groupName, finishCallback, options) => {
279
+ return () => {
280
+ if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
281
+ const glyphsetData = JSON.parse(xmlhttp.responseText);
282
+ loadGlyphset(region, glyphsetData, glyphurl, groupName, finishCallback, options);
283
+ }
284
+ };
285
+ };
286
+
287
+ //Internal loader for a regular zinc geometry.
288
+ const pointsetloader = (region, localTimeEnabled, localMorphColour, groupName, anatomicalId, renderOrder, finishCallback) => {
289
+ return (geometry, materials) => {
290
+ const newPointset = new (require('./primitives/pointset').Pointset)();
291
+ let material = new THREE.PointsMaterial({ alphaTest: 0.5, size: 10, sizeAttenuation: false });
292
+ if (materials && materials[0]) {
293
+ if (1.0 > materials[0].opacity) {
294
+ material.transparent = true;
295
+ }
296
+ material.opacity = materials[0].opacity;
297
+ material.color = materials[0].color;
298
+ material.morphTargets = localTimeEnabled;
299
+ material.vertexColors = materials[0].vertexColors;
300
+ }
301
+ let options = {};
302
+ options.localTimeEnabled = localTimeEnabled;
303
+ options.localMorphColour = localMorphColour;
304
+ if (newPointset) {
305
+ newPointset.createMesh(geometry, material, options);
306
+ newPointset.setName(groupName);
307
+ region.addZincObject(newPointset);
308
+ newPointset.setDuration(scene.getDuration());
309
+ newPointset.setRenderOrder(renderOrder);
310
+ }
311
+ geometry.dispose();
312
+ --this.toBeDownloaded;
313
+ if (finishCallback != undefined && (typeof finishCallback == 'function'))
314
+ finishCallback(newPointset);
315
+ };
316
+ }
317
+
318
+
319
+ /**
320
+ * Read a STL file into this scene, the geometry will be presented as
321
+ * {@link Zinc.Geometry}.
322
+ *
323
+ * @param {STRING} url - location to the STL file.
324
+ * @param {STRING} groupName - name to assign the geometry's groupname to.
325
+ * @param {Function} finishCallback - Callback function which will be called
326
+ * once the STL geometry is succssfully loaded.
327
+ */
328
+ this.loadSTL = (region, url, groupName, finishCallback) => {
329
+ this.toBeDownloaded += 1;
330
+ const colour = require('./zinc').defaultMaterialColor;
331
+ const opacity = require('./zinc').defaultOpacity;
332
+ const loader = new STLLoader();
333
+ loader.crossOrigin = "Anonymous";
334
+ loader.load(resolveURL(url), meshloader(region, colour, opacity, false,
335
+ false, groupName, undefined, undefined, finishCallback));
336
+ }
337
+
338
+ /**
339
+ * Read a OBJ file into this scene, the geometry will be presented as
340
+ * {@link Zinc.Geometry}.
341
+ *
342
+ * @param {STRING} url - location to the STL file.
343
+ * @param {STRING} groupName - name to assign the geometry's groupname to.
344
+ * @param {Function} finishCallback - Callback function which will be called
345
+ * once the OBJ geometry is succssfully loaded.
346
+ */
347
+ this.loadOBJ = (region, url, groupName, finishCallback) => {
348
+ this.toBeDownloaded += 1;
349
+ const colour = require('./zinc').defaultMaterialColor;
350
+ const opacity = require('./zinc').defaultOpacity;
351
+ const loader = new OBJLoader();
352
+ loader.crossOrigin = "Anonymous";
353
+ loader.load(resolveURL(url), meshloader(region, colour, opacity, false,
354
+ false, groupName, undefined, undefined,finishCallback));
355
+ }
356
+
357
+ //Loader for the OBJ format,
358
+ const objloader = (
359
+ region,
360
+ colour,
361
+ opacity,
362
+ localTimeEnabled,
363
+ localMorphColour,
364
+ groupName,
365
+ finishCallback
366
+ ) => {
367
+ return object => {
368
+ this.toBeDownloaded--;
369
+ object.traverse(child => {
370
+ if (child instanceof THREE.Mesh) {
371
+ const zincGeometry = addMeshToZincGeometry(child, localTimeEnabled, localMorphColour);
372
+ region.addZincObject(zincGeometry);
373
+ if (zincGeometry.morph)
374
+ zincGeometry.morph.name = groupName;
375
+ zincGeometry.groupName = groupName;
376
+ if (finishCallback != undefined && (typeof finishCallback == 'function'))
377
+ finishCallback(zincGeometry);
378
+ }
379
+ });
380
+ };
381
+ }
382
+
383
+ /**
384
+ * Load a geometry into this scene, this is a subsequent called from
385
+ * {@link Zinc.Scene#loadMetadataURL}, although it can be used to read
386
+ * in geometry into the scene externally.
387
+ *
388
+ * @param {String} url - regular json model file providing geometry.
389
+ * @param {Boolean} timeEnabled - Indicate if geometry morphing is enabled.
390
+ * @param {Boolean} morphColour - Indicate if color morphing is enabled.
391
+ * @param {STRING} groupName - name to assign the geometry's groupname to.
392
+ * @param {STRING} fileFormat - name supported formats are STL, OBJ and JSON.
393
+ * @param {Function} finishCallback - Callback function which will be called
394
+ * once the geometry is succssfully loaded in.
395
+ */
396
+ const loadSurfaceURL = (region ,url, timeEnabled, morphColour, groupName, finishCallback, options) => {
397
+ this.toBeDownloaded += 1;
398
+ const colour = require('./zinc').defaultMaterialColor;
399
+ const opacity = require('./zinc').defaultOpacity;
400
+ let localTimeEnabled = 0;
401
+ let isInline = (options && options.isInline) ? options.isInline : false;
402
+ let fileFormat = (options && options.fileFormat) ? options.fileFormat : undefined;
403
+ let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
404
+ let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
405
+ if (timeEnabled != undefined)
406
+ localTimeEnabled = timeEnabled ? true : false;
407
+ let localMorphColour = 0;
408
+ if (morphColour != undefined)
409
+ localMorphColour = morphColour ? true : false;
410
+ let loader = primitivesLoader;
411
+ if (fileFormat !== undefined) {
412
+ if (fileFormat == "STL") {
413
+ loader = new STLLoader();
414
+ } else if (fileFormat == "OBJ") {
415
+ loader = new OBJLoader();
416
+ loader.crossOrigin = "Anonymous";
417
+ loader.load(url, objloader(region, colour, opacity, localTimeEnabled,
418
+ localMorphColour, groupName, anatomicalId, finishCallback), this.onProgress(i), this.onError);
419
+ return;
420
+ }
421
+ }
422
+ if (isInline) {
423
+ const object = primitivesLoader.parse( url );
424
+ (meshloader(region, colour, opacity, localTimeEnabled,
425
+ localMorphColour, groupName, anatomicalId, renderOrder, finishCallback))( object.geometry, object.materials );
426
+ } else {
427
+ loader.crossOrigin = "Anonymous";
428
+ primitivesLoader.load(url, meshloader(region, colour, opacity, localTimeEnabled,
429
+ localMorphColour, groupName, anatomicalId, renderOrder, finishCallback), this.onProgress(i), this.onError(finishCallback));
430
+ }
431
+ };
432
+
433
+ //Object to keep track of number of items downloaded and when all items are downloaded
434
+ //allCompletedCallback is called
435
+ const metaFinishCallback = function (numberOfDownloaded, finishCallback, allCompletedCallback) {
436
+ let downloadedItem = 0;
437
+ return zincObject => {
438
+ downloadedItem = downloadedItem + 1;
439
+ if (zincObject && (finishCallback != undefined) && (typeof finishCallback == 'function')) {
440
+ finishCallback(zincObject);
441
+ let zincCameraControls = scene.getZincCameraControls();
442
+ if (zincCameraControls)
443
+ zincCameraControls.calculateMaxAllowedDistance(scene);
444
+ }
445
+ if (downloadedItem == numberOfDownloaded) {
446
+ if (viewLoaded === false)
447
+ scene.viewAll();
448
+ if (allCompletedCallback != undefined && (typeof allCompletedCallback == 'function'))
449
+ allCompletedCallback();
450
+ }
451
+ };
452
+ };
453
+
454
+ /**
455
+ * Load a pointset into this scene object.
456
+ *
457
+ * @param {String} metaurl - Provide informations such as transformations, colours
458
+ * and others for each of the glyph in the glyphsset.
459
+ * @param {Boolean} timeEnabled - Indicate if morphing is enabled.
460
+ * @param {Boolean} morphColour - Indicate if color morphing is enabled.
461
+ * @param {STRING} groupName - name to assign the pointset's groupname to.
462
+ * @param {Function} finishCallback - Callback function which will be called
463
+ * once the glyphset is succssfully load in.
464
+ */
465
+ this.loadPointsetURL = (region, url, timeEnabled, morphColour, groupName, finishCallback, options) => {
466
+ let localTimeEnabled = 0;
467
+ this.toBeDownloaded += 1;
468
+ if (timeEnabled != undefined)
469
+ localTimeEnabled = timeEnabled ? true : false;
470
+ let localMorphColour = 0;
471
+ if (morphColour != undefined)
472
+ localMorphColour = morphColour ? true : false;
473
+ let isInline = (options && options.isInline) ? options.isInline : false;
474
+ let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
475
+ let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
476
+ if (isInline) {
477
+ const object = primitivesLoader.parse( url );
478
+ (pointsetloader(region, localTimeEnabled, localMorphColour, groupName,
479
+ anatomicalId, renderOrder, finishCallback))(object.geometry, object.materials );
480
+ } else {
481
+ primitivesLoader.load(url, pointsetloader(region, localTimeEnabled, localMorphColour,
482
+ groupName, anatomicalId, renderOrder, finishCallback),
483
+ this.onProgress(i), this.onError(finishCallback));
484
+ }
485
+ }
486
+
487
+ /**
488
+ * Load a glyphset into this scene object.
489
+ *
490
+ * @param {String} metaurl - Provide informations such as transformations, colours
491
+ * and others for each of the glyph in the glyphsset.
492
+ * @param {String} glyphurl - regular json model file providing geometry of the glyph.
493
+ * @param {String} groupName - name to assign the glyphset's groupname to.
494
+ * @param {Function} finishCallback - Callback function which will be called
495
+ * once the glyphset is succssfully load in.
496
+ */
497
+ this.loadGlyphsetURL = (region, metaurl, glyphurl, groupName, finishCallback, options) => {
498
+ const isInline = (options && options.isInline) ? options.isInline : false;
499
+ if (isInline) {
500
+ loadGlyphset(region, metaurl, glyphurl, groupName, finishCallback, options);
501
+ } else {
502
+ const xmlhttp = new XMLHttpRequest();
503
+ xmlhttp.onreadystatechange = onLoadGlyphsetReady(region, xmlhttp, glyphurl,
504
+ groupName, finishCallback, options);
505
+ xmlhttp.open("GET", resolveURL(metaurl), true);
506
+ xmlhttp.send();
507
+ }
508
+ }
509
+
510
+ /**
511
+ * Add a user provided {THREE.Geometry} into the scene as zinc geometry.
512
+ *
513
+ * @param {Three.Geometry} geometry - The threejs geometry to be added as {@link Zinc.Geometry}.
514
+ * @param {THREE.Color} color - Colour to be assigned to this geometry, overrided if materialIn is provided.
515
+ * @param {Number} opacity - Opacity to be set for this geometry, overrided if materialIn is provided.
516
+ * @param {Boolean} localTimeEnabled - Set this to true if morph geometry is present, overrided if materialIn is provided.
517
+ * @param {Boolean} localMorphColour - Set this to true if morph colour is present, overrided if materialIn is provided.
518
+ * @param {Boolean} external - Set this to true if morph geometry is present, overrided if materialIn is provided.
519
+ * @param {Function} finishCallback - Callback once the geometry has been added succssfully.
520
+ * @param {THREE.Material} materialIn - Material to be set for this geometry if it is present.
521
+ *
522
+ * @returns {Zinc.Geometry}
523
+ */
524
+ addZincGeometry = (
525
+ region,
526
+ geometryIn,
527
+ colour,
528
+ opacity,
529
+ localTimeEnabled,
530
+ localMorphColour,
531
+ finishCallback,
532
+ materialIn,
533
+ groupName
534
+ ) => {
535
+ let options = {};
536
+ options.colour = colour;
537
+ options.opacity = opacity;
538
+ options.localTimeEnabled = localTimeEnabled;
539
+ options.localMorphColour = localMorphColour
540
+ const newGeometry = new (require('./primitives/geometry').Geometry)();
541
+ newGeometry.createMesh(geometryIn, materialIn, options);
542
+ if (newGeometry.morph) {
543
+ newGeometry.setName(groupName);
544
+ if (region) region.addZincObject(newGeometry);
545
+ newGeometry.setDuration(scene.getDuration());
546
+ if (finishCallback != undefined && (typeof finishCallback == 'function'))
547
+ finishCallback(newGeometry);
548
+ if (newGeometry.videoHandler)
549
+ scene.setVideoHandler(newGeometry.videoHandler);
550
+ return newGeometry;
551
+ }
552
+ return undefined;
553
+ }
554
+
555
+ //Internal loader for a regular zinc geometry.
556
+ const meshloader = (
557
+ region,
558
+ colour,
559
+ opacity,
560
+ localTimeEnabled,
561
+ localMorphColour,
562
+ groupName,
563
+ anatomicalId,
564
+ renderOrder,
565
+ finishCallback
566
+ ) => {
567
+ return (geometry, materials) => {
568
+ let material = undefined;
569
+ if (materials && materials[0]) {
570
+ material = materials[0];
571
+ }
572
+ const zincGeometry = addZincGeometry(region, geometry, colour, opacity,
573
+ localTimeEnabled, localMorphColour, undefined, material, groupName, renderOrder);
574
+ zincGeometry.anatomicalId = anatomicalId;
575
+ zincGeometry.setRenderOrder(renderOrder);
576
+ --this.toBeDownloaded;
577
+ geometry.dispose();
578
+ if (finishCallback != undefined && (typeof finishCallback == 'function'))
579
+ finishCallback(zincGeometry);
580
+ };
581
+ }
582
+
583
+ //Turn ISO 8601 duration string into an array.
584
+ const parseDuration = (durationString) => {
585
+ const regex = /P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
586
+ const [, years, months, weeks, days, hours, mins, secs] =
587
+ durationString.match(regex);
588
+ return {years: years,months: months, weeks: weeks, days: days,
589
+ hours: hours, mins: mins, secs: secs };
590
+ }
591
+
592
+ //Load settings from metadata item.
593
+ this.loadSettings = (item) => {
594
+ if (item) {
595
+ //duration uses the ISO 8601 standard - PnYnMnDTnHnMnS
596
+ if (item.Duration) {
597
+ const duration = parseDuration(item.Duration);
598
+ scene.setDurationFromObject(duration);
599
+ }
600
+ if (item.OriginalDuration) {
601
+ const duration = parseDuration(item.OriginalDuration);
602
+ scene.setOriginalDurationFromObject(duration);
603
+ }
604
+ if (item.TimeStamps) {
605
+ for (const key in item.TimeStamps) {
606
+ const time = parseDuration(item.TimeStamps[key]);
607
+ scene.addMetadataTimeStamp(key, time);
608
+ }
609
+ }
610
+ }
611
+ }
612
+
613
+ //Function to process each of the graphical metadata item except for view and
614
+ //settings.
615
+ const readPrimitivesItem = (region, referenceURL, item, order, finishCallback) => {
616
+ if (item) {
617
+ let newURL = undefined;
618
+ let isInline = false;
619
+ if (item.URL) {
620
+ newURL = item.URL;
621
+ if (referenceURL)
622
+ newURL = createNewURL(item.URL, referenceURL);
623
+ } else if (item.Inline) {
624
+ newURL = item.Inline.URL;
625
+ isInline = true;
626
+ }
627
+ let groupName = item.GroupName;
628
+ if (groupName === undefined || groupName === "") {
629
+ groupName = "_Unnamed";
630
+ }
631
+
632
+ let options = {
633
+ isInline: isInline,
634
+ fileFormat: item.FileFormat,
635
+ anatomicalId: item.AnatomicalId,
636
+ compression: item.compression,
637
+ renderOrder: order
638
+ };
639
+
640
+ switch (item.Type) {
641
+ case "Surfaces":
642
+ loadSurfaceURL(region, newURL, item.MorphVertices, item.MorphColours, groupName, finishCallback, options);
643
+ break;
644
+ case "Glyph":
645
+ let newGeometryURL = undefined;
646
+ if (!isInline) {
647
+ newGeometryURL = item.GlyphGeometriesURL;
648
+ newGeometryURL = createNewURL(item.GlyphGeometriesURL, referenceURL);
649
+ } else {
650
+ newGeometryURL = item.Inline.GlyphGeometriesURL;
651
+ }
652
+ if (item.DisplayLabels) {
653
+ options.displayLabels = true;
654
+ }
655
+ this.loadGlyphsetURL(region, newURL, newGeometryURL, groupName, finishCallback, options);
656
+ break;
657
+ case "Points":
658
+ this.loadPointsetURL(region, newURL, item.MorphVertices, item.MorphColours, groupName, finishCallback, options);
659
+ break;
660
+ case "Lines":
661
+ this.loadLinesURL(region, newURL, item.MorphVertices, item.MorphColours, groupName, finishCallback, options);
662
+ break;
663
+ default:
664
+ break;
665
+ }
666
+ }
667
+ };
668
+
669
+ //Function to read the view item first
670
+ const readViewAndSettingsItem = (referenceURL, item, finishCallback) => {
671
+ if (item) {
672
+ let newURL = undefined;
673
+ let isInline = false;
674
+ if (item.URL) {
675
+ newURL = item.URL;
676
+ if (referenceURL)
677
+ newURL = createNewURL(item.URL, referenceURL);
678
+ } else if (item.Inline) {
679
+ newURL = item.Inline.URL;
680
+ isInline = true;
681
+ }
682
+ switch (item.Type) {
683
+ case "View":
684
+ if (isInline) {
685
+ scene.setupMultipleViews("default", { "default" : newURL});
686
+ viewLoaded = true;
687
+ if (finishCallback != undefined && (typeof finishCallback == 'function'))
688
+ finishCallback();
689
+ }
690
+ else
691
+ this.loadViewURL(newURL, finishCallback);
692
+ break;
693
+ case "Settings":
694
+ this.loadSettings(item);
695
+ break;
696
+ default:
697
+ break;
698
+ }
699
+ }
700
+ };
701
+
702
+ /**
703
+ * Load GLTF into this scene object.
704
+ *
705
+ * @param {String} url - URL to the GLTF file
706
+ * @param {Function} finishCallback - Callback function which will be called
707
+ * once the glyphset is succssfully load in.
708
+ */
709
+ this.loadGLTF = (region, url, finishCallback, options) => {
710
+ const GLTFToZincJSLoader = new (require('./loaders/GLTFToZincJSLoader').GLTFToZincJSLoader)();
711
+ GLTFToZincJSLoader.load(scene, region, url, finishCallback, options);
712
+ }
713
+
714
+ let loadRegions = (currentRegion, referenceURL, regions, callback) => {
715
+ if (regions.Primitives) {
716
+ regions.Primitives.forEach(primitive => {
717
+ let order = 1;
718
+ if (primitive.Order)
719
+ order = primitive.Order;
720
+ readPrimitivesItem(currentRegion, referenceURL, primitive, order, callback);
721
+ });
722
+ }
723
+ if (regions.Transformation) {
724
+ currentRegion.setTransformation(regions.Transformation);
725
+ }
726
+ if (regions.Children) {
727
+ for (const [regionName, value] of Object.entries(regions.Children)) {
728
+ const childRegion = currentRegion.findOrCreateChildFromPath(regionName);
729
+ if (childRegion) {
730
+ loadRegions(childRegion, referenceURL, value, callback);
731
+ }
732
+ }
733
+ }
734
+ }
735
+
736
+ let getNumberOfDownloadsInArray = (array, includeViews) => {
737
+ if (Array.isArray(array)) {
738
+ let count = 0;
739
+ for (let i = 0; i < array.length; i++) {
740
+ if (array[i].Type && (
741
+ (includeViews && array[i].Type === "View") ||
742
+ array[i].Type === "Surfaces" ||
743
+ array[i].Type === "Glyph" ||
744
+ array[i].Type === "Points" ||
745
+ array[i].Type === "Lines"))
746
+ {
747
+ count++;
748
+ }
749
+ }
750
+ return count;
751
+ }
752
+ return 0;
753
+ }
754
+
755
+ let getNumberOfObjectsInRegions = (regionJson) => {
756
+ let counts = regionJson.Primitives ?
757
+ getNumberOfDownloadsInArray(regionJson.Primitives, false) : 0;
758
+ if (regionJson.Children) {
759
+ Object.values(regionJson.Children).forEach(childRegion => {
760
+ counts += getNumberOfObjectsInRegions(childRegion);
761
+ });
762
+ }
763
+ return counts;
764
+ }
765
+
766
+ let getNumberOfObjects = (metadata) => {
767
+ if (Array.isArray(metadata)) {
768
+ return getNumberOfDownloadsInArray(metadata, true);
769
+ } else if ((typeof metadata) === "object" && metadata !== null) {
770
+ if (metadata.Version === "2.0") {
771
+ return getNumberOfObjectsInRegions(metadata.Regions);
772
+ }
773
+ }
774
+ }
775
+
776
+ let readVersionOneRegionPath = (region, referenceURL, item, order, callback) => {
777
+ let targetRegion = region;
778
+ if (item.RegionPath && item.RegionPath !== "") {
779
+ targetRegion = region.findOrCreateChildFromPath(item.RegionPath);
780
+ }
781
+ //Render order is set to i * 2 to account for front and back rendering
782
+ readPrimitivesItem(targetRegion, referenceURL, item, order * 2, callback);
783
+ }
784
+
785
+ let loadVersionOne = (targetRegion, metadata, referenceURL, finishCallback, allCompletedCallback) => {
786
+ let numberOfObjects = getNumberOfObjects(metadata);
787
+ // view file does not receive callback
788
+ let callback = new metaFinishCallback(numberOfObjects, finishCallback, allCompletedCallback);
789
+ // Prioritise the view file and settings before loading anything else
790
+ for (let i = 0; i < metadata.length; i++)
791
+ readViewAndSettingsItem(referenceURL, metadata[i], callback);
792
+ for (let i = 0; i < metadata.length; i++) {
793
+ readVersionOneRegionPath(targetRegion, referenceURL, metadata[i], i, callback);
794
+ }
795
+ }
796
+
797
+ let loadVersionTwo = (targetRegion, metadata, referenceURL, finishCallback, allCompletedCallback) => {
798
+ let numberOfObjects = getNumberOfObjects(metadata);
799
+ // view file does not receive callback
800
+ let callback = new metaFinishCallback(numberOfObjects, finishCallback, allCompletedCallback);
801
+ if (metadata.Settings)
802
+ this.loadSettings(metadata.Settings);
803
+ if (metadata.Views)
804
+ loadMultipleViews(referenceURL, metadata.Views, referenceURL);
805
+ if (metadata.Regions)
806
+ loadRegions(targetRegion, referenceURL, metadata.Regions, callback);
807
+ }
808
+
809
+ /**
810
+ * Load a metadata file from the provided URL into this scene. Once
811
+ * succssful scene proceeds to read each items into scene for visualisations.
812
+ *
813
+ * @param {String} url - Location of the metafile
814
+ * @param {Function} finishCallback - Callback function which will be called
815
+ * for each glyphset and geometry that has been written in.
816
+ */
817
+ this.loadMetadataURL = (targetRegion, url, finishCallback, allCompletedCallback) => {
818
+ const xmlhttp = new XMLHttpRequest();
819
+ var requestURL = resolveURL(url);
820
+ xmlhttp.onreadystatechange = () => {
821
+ if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
822
+ scene.resetMetadata();
823
+ scene.resetDuration();
824
+ viewLoaded = false;
825
+ let referenceURL = xmlhttp.responseURL;
826
+ if (referenceURL === undefined)
827
+ referenceURL = (new URL(requestURL)).href;
828
+ const metadata = JSON.parse(xmlhttp.responseText);
829
+ if (Array.isArray(metadata)) {
830
+ loadVersionOne(targetRegion, metadata, referenceURL, finishCallback, allCompletedCallback);
831
+ } else if (typeof metadata === "object" && metadata !== null) {
832
+ if (metadata.Version == "2.0") {
833
+ loadVersionTwo(targetRegion, metadata, referenceURL, finishCallback, allCompletedCallback);
834
+ }
835
+ }
836
+ }
837
+ }
838
+
839
+ xmlhttp.open("GET", requestURL, true);
840
+ xmlhttp.send();
841
+ }
842
+ }