const THREE = require('three');
const resolveURL = require('./utilities').resolveURL;
const createNewURL = require('./utilities').createNewURL;
const STLLoader = require('./loaders/STLLoader').STLLoader;
const OBJLoader = require('./loaders/OBJLoader').OBJLoader;
const PrimitivesLoader = require('./loaders/primitivesLoader').PrimitivesLoader;
/**
* A helper class to help with reading / importing primitives and
* settings into a {@link Scene}.
*
* @class
* @param {Object} containerIn - Container to create the renderer on.
* @author Alan Wu
* @return {SceneLoader}
*/
exports.SceneLoader = function (sceneIn) {
const scene = sceneIn;
this.toBeDownloaded = 0;
this.progressMap = {};
let viewLoaded = false;
let errorDownload = false;
const primitivesLoader = new PrimitivesLoader();
/**
* This function returns a three component array, which contains
* [totalsize, totalLoaded and errorDownload] of all the downloads happening
* in this scene.
* @returns {Array}
*/
this.getDownloadProgress = () => {
let totalSize = 0;
let totalLoaded = 0;
let unknownFound = false;
for (const key in this.progressMap) {
const progress = this.progressMap[key];
totalSize += progress[1];
totalLoaded += progress[0];
if (progress[1] == 0)
unknownFound = true;
}
if (unknownFound) {
totalSize = 0;
}
return [ totalSize, totalLoaded, errorDownload ];
}
//Stores the current progress of downloads
this.onProgress = id => {
return xhr => {
this.progressMap[id] = [ xhr.loaded, xhr.total ];
};
}
this.onError = finishCallback => {
return xhr => {
this.toBeDownloaded = this.toBeDownloaded - 1;
errorDownload = true;
console.error(`There is an issue with external resource: ${xhr?.responseURL}.`);
const payload = {
type: "Error",
xhr
};
if (finishCallback) {
finishCallback(payload);
}
}
};
let loadMultipleViews = (referenceURL, views) => {
const defaultView = views.Default;
if (views.Inline) {
scene.setupMultipleViews(defaultView, views.Entries);
} else {
const promises = [];
for (const [key, value] of Object.entries(views.Entries)) {
if (referenceURL) {
newURL = createNewURL(value, referenceURL);
promises.push(new Promise((resolve, reject) => {
// Add parameters if we are sent them
fetch(newURL)
.then(response => response.json())
.then(data => resolve({key: key, data: data}))
.catch(data => reject(data));
}));
}
}
Promise.all(promises)
.then(values => {
const entries = {};
values.forEach(entry => {
entries[entry.key] = entry.data;
});
scene.setupMultipleViews(defaultView, entries);
let zincCameraControls = scene.getZincCameraControls();
if (zincCameraControls)
zincCameraControls.setCurrentViewport(defaultView);
viewLoaded = true;
});
}
}
/**
* Load the viewport from an external location provided by the url.
* @param {String} URL - address to the file containing viewport information.
*/
this.loadViewURL = (url, finishCallback) => {
this.toBeDownloaded += 1;
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState == 4) {
if(xmlhttp.status == 200) {
const viewData = JSON.parse(xmlhttp.responseText);
scene.setupMultipleViews("default", { "default" : viewData });
scene.resetView();
viewLoaded = true;
--this.toBeDownloaded;
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback();
} else {
this.onError();
}
}
}
const requestURL = resolveURL(url);
xmlhttp.open("GET", requestURL, true);
xmlhttp.send();
}
/**
* Load a legacy model(s) format with the provided URLs and parameters. This only loads the geometry
* without any of the metadata. Therefore, extra parameters should be provided.
*
* @deprecated
*/
this.loadModelsURL = (region, urls, colours, opacities, timeEnabled, morphColour, finishCallback) => {
const number = urls.length;
this.toBeDownloaded += number;
for (let i = 0; i < number; i++) {
const filename = urls[i];
let colour = require('./zinc').defaultMaterialColor;
let opacity = require('./zinc').defaultOpacity;
if (colours != undefined && colours[i] != undefined)
colour = colours[i] ? true : false;
if (opacities != undefined && opacities[i] != undefined)
opacity = opacities[i];
let localTimeEnabled = 0;
if (timeEnabled != undefined && timeEnabled[i] != undefined)
localTimeEnabled = timeEnabled[i] ? true : false;
let localMorphColour = 0;
if (morphColour != undefined && morphColour[i] != undefined)
localMorphColour = morphColour[i] ? true : false;
primitivesLoader.load(resolveURL(filename), meshloader(region, colour, opacity, localTimeEnabled, localMorphColour, undefined, undefined,
undefined, undefined, finishCallback), this.onProgress(filename), this.onError(finishCallback));
}
}
/**
* Load a legacy file format containing the viewport and its meta file from an external
* location provided by the url. Use the new metadata format with
* {@link Zinc.SceneLoader.#loadMetadataURL} instead.
*
* @param {String} URL - address to the file containing viewport and model information.
* @deprecated
*/
this.loadFromViewURL = (targetRegion, jsonFilePrefix, finishCallback) => {
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
const viewData = JSON.parse(xmlhttp.responseText);
scene.loadView(viewData);
const urls = [];
const filename_prefix = jsonFilePrefix + "_";
for (let i = 0; i < viewData.numberOfResources; i++) {
const filename = filename_prefix + (i + 1) + ".json";
urls.push(filename);
}
this.loadModelsURL(targetRegion, urls, viewData.colour, viewData.opacity, viewData.timeEnabled, viewData.morphColour, finishCallback);
}
}
const requestURL = resolveURL(jsonFilePrefix + "_view.json");
xmlhttp.open("GET", requestURL, true);
xmlhttp.send();
}
//Internal loader for a regular zinc geometry.
const linesloader = (region, localTimeEnabled, localMorphColour, groupName,
anatomicalId, renderOrder, lod, finishCallback) => {
return (geometry, materials) => {
const newLines = new (require('./primitives/lines').Lines)();
let material = undefined;
if (materials && materials[0]) {
material = new THREE.LineBasicMaterial({color:materials[0].color.clone()});
if (1.0 > materials[0].opacity) {
material.transparent = true;
}
material.opacity = materials[0].opacity;
material.morphTargets = localTimeEnabled;
material.vertexColors = materials[0].vertexColors;
}
let options = {};
options.localTimeEnabled = localTimeEnabled;
options.localMorphColour = localMorphColour;
if (newLines) {
newLines.createLineSegment(geometry, material, options);
newLines.setName(groupName);
newLines.anatomicalId = anatomicalId;
newLines.setRenderOrder(renderOrder);
region.addZincObject(newLines);
newLines.setDuration(scene.getDuration());
console.log(lod)
if (lod && lod.levels) {
for (const [key, value] of Object.entries(lod.levels)) {
newLines.addLOD(primitivesLoader, key, value.URL, value.Index, lod.preload);
}
}
}
--this.toBeDownloaded;
geometry.dispose();
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback(newLines);
};
}
/**
* Load lines into this scene object.
*
* @param {Boolean} timeEnabled - Indicate if morphing is enabled.
* @param {Boolean} morphColour - Indicate if color morphing is enabled.
* @param {STRING} groupName - name to assign the pointset's groupname to.
* @param {Function} finishCallback - Callback function which will be called
* once the glyphset is succssfully load in.
*/
this.loadLinesURL = (region, url, timeEnabled, morphColour, groupName, finishCallback, options) => {
let localTimeEnabled = 0;
this.toBeDownloaded += 1;
let isInline = (options && options.isInline) ? options.isInline : false;
let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
if (timeEnabled != undefined)
localTimeEnabled = timeEnabled ? true : false;
let localMorphColour = 0;
if (morphColour != undefined)
localMorphColour = morphColour ? true : false;
if (isInline) {
let object = primitivesLoader.parse( url );
(linesloader(region, localTimeEnabled, localMorphColour, groupName, anatomicalId,
renderOrder, options.lod, finishCallback))( object.geometry, object.materials );
} else {
primitivesLoader.load(url, linesloader(region, localTimeEnabled, localMorphColour, groupName,
anatomicalId, renderOrder, options.lod, finishCallback), this.onProgress(url), this.onError(finishCallback),
options.loaderOptions);
}
}
const loadGlyphset = (region, glyphsetData, glyphurl, groupName, finishCallback, options) => {
let isInline = (options && options.isInline) ? options.isInline : undefined;
let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
let displayLabels = (options && options.displayLabels) ? options.displayLabels : undefined;
let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
const newGlyphset = new (require('./primitives/glyphset').Glyphset)();
newGlyphset.setDuration(scene.getDuration());
newGlyphset.groupName = groupName;
let myCallback = () => {
--this.toBeDownloaded;
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback(newGlyphset);
}
++this.toBeDownloaded;
if (isInline) {
newGlyphset.load(glyphsetData, glyphurl, myCallback, isInline, displayLabels);
}
else {
newGlyphset.load(glyphsetData, resolveURL(glyphurl), myCallback, isInline, displayLabels);
}
newGlyphset.anatomicalId = anatomicalId;
newGlyphset.setRenderOrder(renderOrder);
region.addZincObject(newGlyphset);
};
//Load a glyphset into this scene.
const onLoadGlyphsetReady = (region, xmlhttp, glyphurl, groupName, finishCallback, options) => {
return () => {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
const glyphsetData = JSON.parse(xmlhttp.responseText);
loadGlyphset(region, glyphsetData, glyphurl, groupName, finishCallback, options);
}
};
};
//Internal loader for zinc pointset.
const pointsetloader = (region, localTimeEnabled, localMorphColour, groupName, anatomicalId, renderOrder, finishCallback) => {
return (geometry, materials) => {
const newPointset = new (require('./primitives/pointset').Pointset)();
let material = new THREE.PointsMaterial({ alphaTest: 0.5, size: 10, sizeAttenuation: false });
if (materials && materials[0]) {
if (1.0 > materials[0].opacity) {
material.transparent = true;
}
material.opacity = materials[0].opacity;
material.color = materials[0].color;
material.morphTargets = localTimeEnabled;
material.vertexColors = materials[0].vertexColors;
}
let options = {};
options.localTimeEnabled = localTimeEnabled;
options.localMorphColour = localMorphColour;
if (newPointset) {
newPointset.createMesh(geometry, material, options);
newPointset.setName(groupName);
newPointset.anatomicalId = anatomicalId;
region.addZincObject(newPointset);
newPointset.setDuration(scene.getDuration());
newPointset.setRenderOrder(renderOrder);
}
geometry.dispose();
--this.toBeDownloaded;
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback(newPointset);
};
}
/**
* Read a STL file into this scene, the geometry will be presented as
* {@link Zinc.Geometry}.
*
* @param {STRING} url - location to the STL file.
* @param {STRING} groupName - name to assign the geometry's groupname to.
* @param {Function} finishCallback - Callback function which will be called
* once the STL geometry is succssfully loaded.
*/
this.loadSTL = (region, url, groupName, finishCallback) => {
this.toBeDownloaded += 1;
const colour = require('./zinc').defaultMaterialColor;
const opacity = require('./zinc').defaultOpacity;
const loader = new STLLoader();
loader.crossOrigin = "Anonymous";
loader.load(resolveURL(url), meshloader(region, colour, opacity, false,
false, groupName, undefined, undefined, undefined, finishCallback));
}
/**
* Read a OBJ file into this scene, the geometry will be presented as
* {@link Zinc.Geometry}.
*
* @param {STRING} url - location to the STL file.
* @param {STRING} groupName - name to assign the geometry's groupname to.
* @param {Function} finishCallback - Callback function which will be called
* once the OBJ geometry is succssfully loaded.
*/
this.loadOBJ = (region, url, groupName, finishCallback) => {
this.toBeDownloaded += 1;
const colour = require('./zinc').defaultMaterialColor;
const opacity = require('./zinc').defaultOpacity;
const loader = new OBJLoader();
loader.crossOrigin = "Anonymous";
loader.load(resolveURL(url), meshloader(region, colour, opacity, false,
false, groupName, undefined, undefined, undefined, finishCallback));
}
/**
* Load a geometry into this scene, this is a subsequent called from
* {@link Zinc.Scene#loadMetadataURL}, although it can be used to read
* in geometry into the scene externally.
*
* @param {String} url - regular json model file providing geometry.
* @param {Boolean} timeEnabled - Indicate if geometry morphing is enabled.
* @param {Boolean} morphColour - Indicate if color morphing is enabled.
* @param {STRING} groupName - name to assign the geometry's groupname to.
* @param {STRING} fileFormat - name supported formats are STL, OBJ and JSON.
* @param {Function} finishCallback - Callback function which will be called
* once the geometry is succssfully loaded in.
*/
const loadSurfaceURL = (region ,url, timeEnabled, morphColour, groupName, finishCallback, options) => {
this.toBeDownloaded += 1;
const colour = require('./zinc').defaultMaterialColor;
const opacity = require('./zinc').defaultOpacity;
let localTimeEnabled = 0;
let isInline = (options && options.isInline) ? options.isInline : false;
let fileFormat = (options && options.fileFormat) ? options.fileFormat : undefined;
let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
if (timeEnabled != undefined)
localTimeEnabled = timeEnabled ? true : false;
let localMorphColour = 0;
if (morphColour != undefined)
localMorphColour = morphColour ? true : false;
let loader = primitivesLoader;
if (fileFormat !== undefined) {
if (fileFormat == "STL") {
loader = new STLLoader();
} else if (fileFormat == "OBJ") {
loader = new OBJLoader();
loader.crossOrigin = "Anonymous";
loader.load(url, objloader(region, colour, opacity, localTimeEnabled,
localMorphColour, groupName, anatomicalId, finishCallback), this.onProgress(url), this.onError,
options.loaderOptions);
return;
}
}
if (isInline) {
const object = primitivesLoader.parse( url );
(meshloader(region, colour, opacity, localTimeEnabled,
localMorphColour, groupName, anatomicalId, renderOrder, options, finishCallback))( object.geometry, object.materials );
} else {
loader.crossOrigin = "Anonymous";
primitivesLoader.load(url, meshloader(region, colour, opacity, localTimeEnabled,
localMorphColour, groupName, anatomicalId, renderOrder, options, finishCallback),
this.onProgress(url), this.onError(finishCallback), options.loaderOptions);
}
};
//Object to keep track of number of items downloaded and when all items are downloaded
//allCompletedCallback is called
const metaFinishCallback = function (numberOfDownloaded, finishCallback, allCompletedCallback) {
let downloadedItem = 0;
return zincObject => {
downloadedItem = downloadedItem + 1;
if (zincObject && (finishCallback != undefined) && (typeof finishCallback == 'function')) {
finishCallback(zincObject);
let zincCameraControls = scene.getZincCameraControls();
if (zincCameraControls) {
zincCameraControls.calculateMaxAllowedDistance(scene);
}
}
if (downloadedItem == numberOfDownloaded) {
if (viewLoaded === false)
scene.viewAll();
if (allCompletedCallback != undefined && (typeof allCompletedCallback == 'function'))
allCompletedCallback();
}
};
};
/**
* Load a pointset into this scene object.
*
* @param {Boolean} timeEnabled - Indicate if morphing is enabled.
* @param {Boolean} morphColour - Indicate if color morphing is enabled.
* @param {STRING} groupName - name to assign the pointset's groupname to.
* @param {Function} finishCallback - Callback function which will be called
* once the glyphset is succssfully load in.
*/
this.loadPointsetURL = (region, url, timeEnabled, morphColour, groupName, finishCallback, options) => {
let localTimeEnabled = 0;
this.toBeDownloaded += 1;
if (timeEnabled != undefined)
localTimeEnabled = timeEnabled ? true : false;
let localMorphColour = 0;
if (morphColour != undefined)
localMorphColour = morphColour ? true : false;
let isInline = (options && options.isInline) ? options.isInline : false;
let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
if (isInline) {
const object = primitivesLoader.parse( url );
(pointsetloader(region, localTimeEnabled, localMorphColour, groupName,
anatomicalId, renderOrder, finishCallback))(object.geometry, object.materials );
} else {
primitivesLoader.load(url, pointsetloader(region, localTimeEnabled, localMorphColour,
groupName, anatomicalId, renderOrder, finishCallback),
this.onProgress(url), this.onError(finishCallback), options.loaderOptions);
}
}
const loadTexture = (region, referenceURL, textureData, groupName, finishCallback, options) => {
let isInline = (options && options.isInline) ? options.isInline : undefined;
let anatomicalId = (options && options.anatomicalId) ? options.anatomicalId : undefined;
let renderOrder = (options && options.renderOrder) ? options.renderOrder : undefined;
let newTexture = undefined;
if (textureData) {
if (referenceURL && textureData.images && textureData.images.source) {
const source = textureData.images.source;
for (let i = 0; i < source.length; i++) {
const newURL = createNewURL(source[i], referenceURL);
textureData.images.source[i] = newURL;
}
}
if (textureData.type === "slides") {
newTexture = new (require('./primitives/textureSlides').TextureSlides)();
}
if (newTexture) {
newTexture.groupName = groupName;
let myCallback = () => {
--this.toBeDownloaded;
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback(newTexture);
}
++this.toBeDownloaded;
newTexture.load(textureData, myCallback, isInline);
newTexture.anatomicalId = anatomicalId;
newTexture.setRenderOrder(renderOrder);
region.addZincObject(newTexture);
}
}
};
//Load a glyphset into this scene.
const onLoadTextureReady = (region, xmlhttp, groupName, finishCallback, options) => {
return () => {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
const textureData = JSON.parse(xmlhttp.responseText);
loadTexture(region, xmlhttp.responseURL, textureData, groupName, finishCallback, options);
}
};
};
/**
* Load a texture into this scene object.
*
* @param {STRING} groupName - name to assign the pointset's groupname to.
* @param {Function} finishCallback - Callback function which will be called
* once the glyphset is succssfully load in.
*/
this.loadTextureURL = (region, url, groupName, finishCallback, options) => {
const isInline = (options && options.isInline) ? options.isInline : false;
if (isInline) {
loadTexture(region, undefined, url, groupName, finishCallback, options);
} else {
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = onLoadTextureReady(region, xmlhttp,
groupName, finishCallback, options);
xmlhttp.open("GET", resolveURL(url), true);
xmlhttp.send();
}
}
/**
* Load a glyphset into this scene object.
*
* @param {String} metaurl - Provide informations such as transformations, colours
* and others for each of the glyph in the glyphsset.
* @param {String} glyphurl - regular json model file providing geometry of the glyph.
* @param {String} groupName - name to assign the glyphset's groupname to.
* @param {Function} finishCallback - Callback function which will be called
* once the glyphset is succssfully load in.
*/
this.loadGlyphsetURL = (region, metaurl, glyphurl, groupName, finishCallback, options) => {
const isInline = (options && options.isInline) ? options.isInline : false;
if (isInline) {
loadGlyphset(region, metaurl, glyphurl, groupName, finishCallback, options);
} else {
const xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = onLoadGlyphsetReady(region, xmlhttp, glyphurl,
groupName, finishCallback, options);
xmlhttp.open("GET", resolveURL(metaurl), true);
xmlhttp.send();
}
}
/**
* Add a user provided {THREE.Geometry} into the scene as zinc geometry.
*
* @param {Three.Geometry} geometry - The threejs geometry to be added as {@link Zinc.Geometry}.
* @param {THREE.Color} color - Colour to be assigned to this geometry, overrided if materialIn is provided.
* @param {Number} opacity - Opacity to be set for this geometry, overrided if materialIn is provided.
* @param {Boolean} localTimeEnabled - Set this to true if morph geometry is present, overrided if materialIn is provided.
* @param {Boolean} localMorphColour - Set this to true if morph colour is present, overrided if materialIn is provided.
* @param {Boolean} external - Set this to true if morph geometry is present, overrided if materialIn is provided.
* @param {Function} finishCallback - Callback once the geometry has been added succssfully.
* @param {THREE.Material} materialIn - Material to be set for this geometry if it is present.
*
* @returns {Zinc.Geometry}
*/
const addZincGeometry = (
region,
geometryIn,
colour,
opacity,
localTimeEnabled,
localMorphColour,
finishCallback,
materialIn,
groupName
) => {
let options = {};
options.colour = colour;
options.opacity = opacity;
options.localTimeEnabled = localTimeEnabled;
options.localMorphColour = localMorphColour
const newGeometry = new (require('./primitives/geometry').Geometry)();
newGeometry.createMesh(geometryIn, materialIn, options);
if (newGeometry.getMorph()) {
newGeometry.setName(groupName);
if (region) region.addZincObject(newGeometry);
newGeometry.setDuration(scene.getDuration());
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback(newGeometry);
if (newGeometry.videoHandler)
scene.setVideoHandler(newGeometry.videoHandler);
return newGeometry;
}
return undefined;
}
//Internal loader for a regular zinc geometry.
const meshloader = (
region,
colour,
opacity,
localTimeEnabled,
localMorphColour,
groupName,
anatomicalId,
renderOrder,
options,
finishCallback
) => {
return (geometry, materials) => {
let material = undefined;
if (materials && materials[0]) {
material = materials[0];
}
const zincGeometry = addZincGeometry(region, geometry, colour, opacity,
localTimeEnabled, localMorphColour, undefined, material, groupName, renderOrder);
zincGeometry.anatomicalId = anatomicalId;
zincGeometry.setRenderOrder(renderOrder);
if (options.lod && options.lod.levels) {
for (const [key, value] of Object.entries(options.lod.levels)) {
zincGeometry.addLOD(primitivesLoader, key, value.URL, value.Index, options.lod.preload);
}
}
--this.toBeDownloaded;
geometry.dispose();
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback(zincGeometry);
};
}
//Turn ISO 8601 duration string into an array.
const parseDuration = (durationString) => {
const regex = /P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
const [, years, months, weeks, days, hours, mins, secs] =
durationString.match(regex);
return {years: years,months: months, weeks: weeks, days: days,
hours: hours, mins: mins, secs: secs };
}
//Load settings from metadata item.
this.loadSettings = (item) => {
if (item) {
//duration uses the ISO 8601 standard - PnYnMnDTnHnMnS
if (item.Duration) {
const duration = parseDuration(item.Duration);
scene.setDurationFromObject(duration);
}
if (item.OriginalDuration) {
const duration = parseDuration(item.OriginalDuration);
scene.setOriginalDurationFromObject(duration);
}
if (item.TimeStamps) {
for (const key in item.TimeStamps) {
const time = parseDuration(item.TimeStamps[key]);
scene.addMetadataTimeStamp(key, time);
}
}
}
}
//Function to process each of the graphical metadata item except for view and
//settings.
const readPrimitivesItem = (region, referenceURL, item, order, finishCallback) => {
if (item) {
let newURL = undefined;
let isInline = false;
if (item.URL) {
//Convert it into an array
newURL = item.URL;
if (referenceURL)
newURL = createNewURL(newURL, referenceURL);
} else if (item.Inline) {
newURL = item.Inline.URL;
isInline = true;
}
const lod = {};
if (item.LOD && item.LOD.Levels) {
lod.preload = item.LOD.Preload ? true : false;
lod.levels = {};
for (const [key, value] of Object.entries(item.LOD.Levels)) {
lod.levels[key] = {};
lod.levels[key]["URL"] = createNewURL(value.URL, referenceURL);
lod.levels[key]["Index"] = value.Index;
}
}
let groupName = item.GroupName;
if (groupName === undefined || groupName === "") {
groupName = "_Unnamed";
}
let options = {
loaderOptions: {
index: item.Index,
},
isInline: isInline,
fileFormat: item.FileFormat,
anatomicalId: item.AnatomicalId,
compression: item.compression,
lod: lod,
renderOrder: order
};
switch (item.Type) {
case "Surfaces":
loadSurfaceURL(region, newURL, item.MorphVertices, item.MorphColours, groupName, finishCallback, options);
break;
case "Glyph":
let newGeometryURL = undefined;
if (!isInline) {
newGeometryURL = item.GlyphGeometriesURL;
newGeometryURL = createNewURL(item.GlyphGeometriesURL, referenceURL);
} else {
newGeometryURL = item.Inline.GlyphGeometriesURL;
}
if (item.DisplayLabels) {
options.displayLabels = true;
}
this.loadGlyphsetURL(region, newURL, newGeometryURL, groupName, finishCallback, options);
break;
case "Points":
this.loadPointsetURL(region, newURL, item.MorphVertices, item.MorphColours, groupName, finishCallback, options);
break;
case "Lines":
this.loadLinesURL(region, newURL, item.MorphVertices, item.MorphColours, groupName, finishCallback, options);
break;
case "Texture":
this.loadTextureURL(region, newURL, groupName, finishCallback, options);
break;
default:
break;
}
}
};
//Function to read the view item first
const readViewAndSettingsItem = (referenceURL, item, finishCallback) => {
if (item) {
let newURL = undefined;
let isInline = false;
if (item.URL) {
newURL = item.URL;
if (referenceURL)
newURL = createNewURL(item.URL, referenceURL);
} else if (item.Inline) {
newURL = item.Inline.URL;
isInline = true;
}
switch (item.Type) {
case "View":
if (isInline) {
scene.setupMultipleViews("default", { "default" : newURL});
viewLoaded = true;
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback();
}
else
this.loadViewURL(newURL, finishCallback);
break;
case "Settings":
this.loadSettings(item);
break;
default:
break;
}
}
};
/**
* Load GLTF into this scene object.
*
* @param {String} url - URL to the GLTF file
* @param {Function} finishCallback - Callback function which will be called
* once the glyphset is succssfully load in.
*/
this.loadGLTF = (region, url, finishCallback, allCompletedCallback, options) => {
const GLTFToZincJSLoader = new (require('./loaders/GLTFToZincJSLoader').GLTFToZincJSLoader)();
GLTFToZincJSLoader.load(scene, region, url, finishCallback, allCompletedCallback, options);
}
let loadRegions = (currentRegion, referenceURL, regions, callback) => {
if (regions.Primitives) {
regions.Primitives.forEach(primitive => {
let order = 1;
if (primitive.Order)
order = primitive.Order;
readPrimitivesItem(currentRegion, referenceURL, primitive, order, callback);
});
}
if (regions.Transformation) {
currentRegion.setTransformation(regions.Transformation);
}
if (regions.Children) {
for (const [regionName, value] of Object.entries(regions.Children)) {
const childRegion = currentRegion.findOrCreateChildFromPath(regionName);
if (childRegion) {
loadRegions(childRegion, referenceURL, value, callback);
}
}
}
}
let getNumberOfDownloadsInArray = (array, includeViews) => {
if (Array.isArray(array)) {
let count = 0;
for (let i = 0; i < array.length; i++) {
if (array[i].Type && (
(includeViews && array[i].Type === "View") ||
array[i].Type === "Surfaces" ||
array[i].Type === "Glyph" ||
array[i].Type === "Points" ||
array[i].Type === "Lines" ||
array[i].Type === "Texture"))
{
count++;
}
}
return count;
}
return 0;
}
let getNumberOfObjectsInRegions = (regionJson) => {
let counts = regionJson.Primitives ?
getNumberOfDownloadsInArray(regionJson.Primitives, false) : 0;
if (regionJson.Children) {
Object.values(regionJson.Children).forEach(childRegion => {
counts += getNumberOfObjectsInRegions(childRegion);
});
}
return counts;
}
let getNumberOfObjects = (metadata) => {
if (Array.isArray(metadata)) {
return getNumberOfDownloadsInArray(metadata, true);
} else if ((typeof metadata) === "object" && metadata !== null) {
if (metadata.Version === "2.0") {
return getNumberOfObjectsInRegions(metadata.Regions);
}
}
}
let readVersionOneRegionPath = (region, referenceURL, item, order, callback) => {
let targetRegion = region;
if (item.RegionPath && item.RegionPath !== "") {
targetRegion = region.findOrCreateChildFromPath(item.RegionPath);
}
//Render order is set to i * 2 to account for front and back rendering
readPrimitivesItem(targetRegion, referenceURL, item, order * 2, callback);
}
let loadVersionOne = (targetRegion, metadata, referenceURL, finishCallback, allCompletedCallback) => {
let numberOfObjects = getNumberOfObjects(metadata);
// view file does not receive callback
let callback = new metaFinishCallback(numberOfObjects, finishCallback, allCompletedCallback);
// Prioritise the view file and settings before loading anything else
for (let i = 0; i < metadata.length; i++)
readViewAndSettingsItem(referenceURL, metadata[i], callback);
for (let i = 0; i < metadata.length; i++) {
readVersionOneRegionPath(targetRegion, referenceURL, metadata[i], i, callback);
}
}
let loadVersionTwo = (targetRegion, metadata, referenceURL, finishCallback, allCompletedCallback) => {
let numberOfObjects = getNumberOfObjects(metadata);
// view file does not receive callback
let callback = new metaFinishCallback(numberOfObjects, finishCallback, allCompletedCallback);
if (metadata.Settings)
this.loadSettings(metadata.Settings);
if (metadata.Views)
loadMultipleViews(referenceURL, metadata.Views, referenceURL);
if (metadata.Regions)
loadRegions(targetRegion, referenceURL, metadata.Regions, callback);
}
/**
* Load a metadata file from the provided URL into this scene. Once
* succssful scene proceeds to read each items into scene for visualisations.
*
* @param {String} url - Location of the metafile
* @param {Function} finishCallback - Callback function which will be called
* for each glyphset and geometry that has been written in.
*/
this.loadMetadataURL = (targetRegion, url, finishCallback, allCompletedCallback) => {
const xmlhttp = new XMLHttpRequest();
const requestURL = resolveURL(url);
xmlhttp.onreadystatechange = () => {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
scene.resetMetadata();
scene.resetDuration();
viewLoaded = false;
let referenceURL = xmlhttp.responseURL;
if (referenceURL === undefined)
referenceURL = (new URL(requestURL)).href;
const metadata = JSON.parse(xmlhttp.responseText);
if (Array.isArray(metadata)) {
loadVersionOne(targetRegion, metadata, referenceURL, finishCallback, allCompletedCallback);
} else if (typeof metadata === "object" && metadata !== null) {
if (metadata.Version == "2.0") {
loadVersionTwo(targetRegion, metadata, referenceURL, finishCallback, allCompletedCallback);
}
}
}
}
xmlhttp.open("GET", requestURL, true);
xmlhttp.send();
}
}