/**
* Viewer of 3D-organs models. Users can toggle on/off different views. Data is displayed instead
* if models are not available.
*
* @class
* @param {PJP.ModelsLoader} ModelsLoaderIn - defined in modelsLoade.js, providing locations of files.
* @param {String} PanelName - Id of the target element to create the {@link PJP.OrgansViewer} on.
* @author Alan Wu
* @returns {PJP.OrgansViewer}
*/
PJP.OrgansViewer = function(ModelsLoaderIn, PanelName) {
var currentSpecies = undefined;
var pickerScene = undefined;
var displayScene = undefined;
var defaultScene = undefined;
var secondaryScene = undefined;
var tertiaryScene = undefined;
var nerveMapScene = undefined;
var windowWidth, windowHeight;
var organGui;
var currentImgZoom = 1.0;
var organGuiControls = new function() {
this.Speed = 500;
};
var imgRightClickDown = false;
var comparedSceneIsOn = false;
var additionalSpecies = undefined;
var currentName = "";
var currentSystem = "";
var currentPart = "";
var currentSpecies = "";
//Current model's associate data, data fields, external link, nerve map informations,
//these are proived in the organsFileMap array.
var associateData = undefined;
var dataFields = undefined;
var externalOrganLink = undefined;
var nerveMap = undefined;
var nerveMapIsActive = false;
var timeoutID = 0;
var fullScreen = false;
var organPartsGui;
// data used by dat.gui to control model specific controls.
var organPartGuiControls = function() {
};
var timeSlider = undefined;
var texSlider = undefined;
var speedSlider = undefined;
// data used by dat.gui to control non-model specific controls.
var organsControl = function() {
this.Background = [ 255, 255, 255 ]; // RGB array
};
/**
* {ImageCombiner}.
*/
var imageCombiner = undefined;
var UIIsReady = false;
var tissueViewer = undefined;
var cellPanel = undefined;
var modelPanel = undefined;
var modelsLoader = ModelsLoaderIn;
var _this = this;
//ZincRenderer for the primary display of model.
var organsRenderer = null;
//Secondary renderer, used for comparing species models.
var secondaryRenderer = null;
//Array for storing different species models urls.
var organsFileMap = new Array();
//Array for storing rat's models urls and its associated data.
var ratOrgansFileMap = new Array();
ratOrgansFileMap["Cardiovascular"] = new Array();
ratOrgansFileMap["Cardiovascular"]["Kidneys-Arteries"] = {
view: "rat/cardiovascular/arteries/rat_kidneys_view.json",
meta: "rat/cardiovascular/arteries/rat_kidneys_1.json",
picker: undefined,
associateData: undefined
};
//Array for storing human's models urls and its associated data.
var humanOrgansFileMap = new Array();
humanOrgansFileMap["Cardiovascular"] = new Array();
humanOrgansFileMap["Digestive"] = new Array();
humanOrgansFileMap["Respiratory"] = new Array();
humanOrgansFileMap["Cardiovascular"]["Heart"] = {
view: "cardiovascular/heart/heart_view.json",
meta: "cardiovascular/heart/animated_nerve_1.json",
picker: "cardiovascular/heart/picking_node_1.json",
associateData: undefined,
externalLink: "https://models.cellml.org/e/bd/deforming_heart.rdf/view"};
humanOrgansFileMap["Cardiovascular"]["Arterial Flow"] = {
view: undefined,
meta: "cardiovascular/arteries/arterial_flow_1.json",
picker: undefined,
associateData: undefined};
humanOrgansFileMap["Cardiovascular"]["Arterial Pressure"] = {
view: undefined,
meta: "cardiovascular/arteries/arterial_pressure_1.json",
picker: undefined,
associateData: undefined};
humanOrgansFileMap["Cardiovascular"]["Arterial Velocity"] = {
view: undefined,
meta: "cardiovascular/arteries/arterial_velocity_1.json",
picker: undefined,
associateData: undefined};
humanOrgansFileMap["Cardiovascular"]["Aorta"] = {
view: undefined,
meta: "cardiovascular/arteries/arteries_1.json",
picker: undefined,
sceneName: "Cardiovascular/Arterial System",
associateData: [{SystemName: "Cardiovascular", PartName: "Aorta"},
{SystemName: "Cardiovascular", PartName: "Left Upper Limb"},
{SystemName: "Cardiovascular", PartName: "Left Lower Limb"},
{SystemName: "Cardiovascular", PartName: "Right Upper Limb"},
{SystemName: "Cardiovascular", PartName: "Right Lower Limb"}],
fields: [{SystemName: "Cardiovascular", PartName: "Arterial Flow"},
{SystemName: "Cardiovascular", PartName: "Arterial Pressure"},
{SystemName: "Cardiovascular", PartName: "Arterial Velocity"}]};
humanOrgansFileMap["Cardiovascular"]["Left Upper Limb"] = humanOrgansFileMap["Cardiovascular"]["Aorta"];
humanOrgansFileMap["Cardiovascular"]["Left Lower Limb"] = humanOrgansFileMap["Cardiovascular"]["Aorta"];
humanOrgansFileMap["Cardiovascular"]["Right Upper Limb"] = humanOrgansFileMap["Cardiovascular"]["Aorta"];
humanOrgansFileMap["Cardiovascular"]["Right Lower Limb"] = humanOrgansFileMap["Cardiovascular"]["Aorta"];
humanOrgansFileMap["Digestive"]["Stomach"] = {
view: undefined,
meta: "digestive/stomach_1.json",
picker: undefined,
associateData: undefined,
nerveMap: new Array()};
humanOrgansFileMap["Digestive"]["Stomach"].nerveMap["threed"] = {meta: "digestive/stomach/nerve_map/3d/stomach_nerve_3d_1.json",
view: "digestive/stomach/nerve_map/3d/stomach_nerve_3d_view.json"};
humanOrgansFileMap["Digestive"]["Stomach"].nerveMap["twod"] = {meta: "digestive/stomach/nerve_map/2d/stomach_nerve_2d_1.json",
view: "digestive/stomach/nerve_map/2d/stomach_nerve_2d_view.json"};
humanOrgansFileMap["Digestive"]["Stomach"].nerveMap["normalised"] = {meta: "digestive/stomach_1.json", view: undefined};
humanOrgansFileMap["Digestive"]["Stomach"].nerveMap["svg"] = {url: 'svg/Myocyte_v6_Grouped.svg'};
humanOrgansFileMap["Respiratory"]["Lungs"] = {
view: "respiratory/lungs_view.json",
meta: "respiratory/lungs_1.json",
picker: undefined,
associateData: undefined};
organsFileMap['human'] = humanOrgansFileMap;
organsFileMap['rat'] = ratOrgansFileMap;
this.setTissueViewer = function(TissueViewerIn) {
tissueViewer = TissueViewerIn;
}
this.setCellPanel = function(CellPanelIn) {
cellPanel = CellPanelIn;
}
this.setModelPanel = function(ModelPanelIn) {
modelPanel = ModelPanelIn;
}
var getPos = function(el) {
for (var lx=0, ly=0;
el != null;
lx += el.offsetLeft, ly += el.offsetTop, el = el.offsetParent);
return {x: lx,y: ly};
}
/**
* Used to update internal timeer in scene when time slider has changed.
*/
var timeSliderChanged = function() {
if (!nerveMapIsActive) {
if (pickerScene)
pickerScene.setMorphsTime(timeSlider.value * 30);
if (displayScene)
displayScene.setMorphsTime(timeSlider.value * 30);
} else if (nerveMapScene) {
nerveMapScene.setMorphsTime(timeSlider.value * 30);
if (nerveMap && nerveMap.additionalReader)
nerveMap.additionalReader.setTime(timeSlider.value / 100.0);
}
}
/**
* Update the time slider and other renderers/scenes when time has changed.
*/
var updateTimeSlider = function() {
var currentTime = organsRenderer.getCurrentTime();
var sliderValue = currentTime / 30.0;
timeSlider.value = sliderValue;
if (!nerveMapIsActive && pickerScene)
pickerScene.setMorphsTime(currentTime);
if (nerveMap && nerveMap.additionalReader)
nerveMap.additionalReader.setTime(currentTime / 3000.0);
}
var updateTimeSliderCallback = function() {
return function() {
updateTimeSlider();
}
}
var playPauseAnimation = function(element) {
if (element.className == "play") {
element.className = "pause";
organsRenderer.playAnimation = true;
} else {
element.className = "play";
organsRenderer.playAnimation = false;
}
}
/**
* Speed slider has moved, adjust the play speed of the renderer.
* @callback
*/
var speedSliderChanged = function() {
return function(value) {
organsRenderer.setPlayRate(value);
}
}
var updateSpeedSlider = function() {
var playRate = organsRenderer.getPlayRate();
organGuiControls.Speed = playRate;
speedSlider.updateDisplay();
}
var texSliderChanged = function() {
if (nerveMap && nerveMap.additionalReader)
nerveMap.additionalReader.setSliderPos(texSlider.value);
}
var setOrgansPanelTitle = function(name) {
var text_display = document.getElementById('OrganTitle');
text_display.innerHTML = "<strong>Organ: <span style='color:#FF4444'>" + name + "</span></strong>";
}
/**
* Callback function when a pickable object has been picked. It will then call functions in tissueViewer
* and cellPanel to show corresponding informations.
*
* @callback
*/
var _pickingCallback = function() {
return function(intersects, window_x, window_y) {
if (intersects[0] !== undefined) {
if (displayScene.sceneName == "human/Cardiovascular/Heart") {
var id = Math.round(intersects[ 0 ].object.material.color.b * 255) ;
setToolTipText("Node " + id);
currentHoverId = id;
showTooltip(window_x, window_y);
var tissueTitle = "<strong>Tissue: <span style='color:#FF4444'>" + id + "</span></strong>";
if (tissueViewer) {
tissueViewer.setTissueTitleString(tissueTitle);
tissueViewer.showButtons(true);
tissueViewer.showCollagenVisible(true);
}
} else if (displayScene.sceneName.includes("human/Cardiovascular/Arterial")) {
setToolTipText("Click to show vascular model");
showTooltip(window_x, window_y);
if (tissueViewer)
tissueViewer.resetTissuePanel();
if (cellPanel)
cellPanel.resetCellPanel();
if (modelPanel)
modelPanel.openModel("BG_Circulation_Model.svg");
}
}
}
};
/**
* Callback function when a pickable object has been hovered over. It will show
* objecty id/name as tooltip text.
*
* @callback
*/
var _hoverCallback = function() {
return function(intersects, window_x, window_y) {
if (intersects[0] !== undefined) {
if (displayScene.sceneName == "human/Cardiovascular/Heart") {
var id = Math.round(intersects[ 0 ].object.material.color.b * 255) ;
setToolTipText("Node " + id);
currentHoverId = id;
document.getElementById("organsDisplayArea").style.cursor = "pointer";
showTooltip(window_x, window_y);
} else if (displayScene.sceneName.includes("human/Cardiovascular/Arterial")) {
document.getElementById("organsDisplayArea").style.cursor = "pointer";
setToolTipText("Click to show vascular model");
showTooltip(window_x, window_y);
}
}
else {
hideTooltip();
document.getElementById("organsDisplayArea").style.cursor = "auto";
}
}
};
var updateOrganPartsVisibilty = function(name, flag) {
return function(zincGeometry) {
if (zincGeometry.groupName && zincGeometry.groupName == name) {
zincGeometry.setVisibility(flag);
}
}
}
/**
* Change visibility for parts of the current scene.
*/
var changeOrganPartsVisibility = function(name, value) {
displayScene.forEachGeometry(updateOrganPartsVisibilty(name, value));
displayScene.forEachGlyphset(updateOrganPartsVisibilty(name, value));
if (pickerScene) {
pickerScene.forEachGeometry(updateOrganPartsVisibilty(name, value))
pickerScene.forEachGlyphset(updateOrganPartsVisibilty(name, value));
}
}
var changeOrganPartsVisibilityCallback = function(name) {
return function(value) {
changeOrganPartsVisibility(name, value);
}
}
var _addDataGeometryCallback = function(GroupName, color) {
return function(geometry) {
geometry.groupName = GroupName;
if (color && geometry.morph)
geometry.morph.material.color = color;
}
}
/**
* Callback function when a data geometry has been toggled on/off the scene.
*/
var changeDataGeometryVisibility = function() {
return function(value) {
if ((displayScene.findGeometriesWithGroupName("Data Geometry").length > 0) ||
(displayScene.findGlyphsetsWithGroupName("Data Geometry").length > 0)) {
changeOrganPartsVisibility("Data Geometry", value);
} else {
for ( var i = 0; i < associateData.length; i ++ ) {
var systemMeta = modelsLoader.getSystemMeta(currentSpecies);
var metaItem = systemMeta[associateData[i].SystemName][associateData[i].PartName];
var downloadPath = metaItem["BodyURL"];
var color = new THREE.Color("#0099ff");
if (metaItem["FileFormat"] == "JSON")
displayScene.loadMetadataURL(downloadPath, _addDataGeometryCallback("Data Geometry", color));
else if (metaItem["FileFormat"] == "STL")
displayScene.loadSTL(downloadPath, "Data Geometry", _addDataGeometryCallback("Data Geometry", color));
else if (metaItem["FileFormat"] == "OBJ")
displayScene.loadOBJ(downloadPath, "Data Geometry", _addDataGeometryCallback("Data Geometry", color));
}
}
}
}
/**
* Update layout of the organs panel, there are three different layouts at this moment.
* 1. Normal display - Fullscreen/split screen with a single display.
* 2. Nerve map display - Three panels when it is on fullscreen display.
* 3. Species comparison display - Two panels when it is on fullscreen display.
*/
var updateLayout = function() {
if (fullScreen) {
if (comparedSceneIsOn) {
var element = document.getElementById("organsDisplayArea");
element.style.width = "50%";
element = document.getElementById("organsSecondaryDisplayArea");
element.className = "organsSecondSceneDisplay";
element.style.display = "block";
element = document.getElementById("timeSliderContainer");
element.style.width = "50%";
element = document.getElementById("organsTertieryDisplayArea");
element.style.display = "none";
} else if (nerveMapIsActive) {
var element = document.getElementById("organsDisplayArea");
element.style.width = "33%";
element = document.getElementById("organsSecondaryDisplayArea");
element.className = "organsSecondNerveDisplay";
element.style.display = "block";
element = document.getElementById("timeSliderContainer");
element.style.width = "33%";
element = document.getElementById("organsTertieryDisplayArea");
element.style.display = "block";
} else {
var element = document.getElementById("organsDisplayArea");
element.style.width = "100%";
element = document.getElementById("timeSliderContainer");
element.style.width = "100%";
element = document.getElementById("organsSecondaryDisplayArea");
element.style.display = "none";
element = document.getElementById("organsTertieryDisplayArea");
element.style.display = "none";
}
} else {
var element = document.getElementById("organsDisplayArea");
element.style.width = "100%";
element = document.getElementById("timeSliderContainer");
element.style.width = "100%";
element = document.getElementById("organsSecondaryDisplayArea");
element.style.display = "none";
element = document.getElementById("organsTertieryDisplayArea");
element.style.display = "none";
}
if (nerveMapIsActive) {
element = document.getElementById("texSlider");
element.style.display = "block";
element = document.getElementById("organsImgContainer");
element.style.display = "block";
element = document.getElementById("organsSecondaryDisplayRenderer");
element.style.display = "none";
element = document.getElementById("CheckboxTree");
element.style.display = "block";
} else {
element = document.getElementById("texSlider");
element.style.display = "none";
element = document.getElementById("organsImgContainer");
element.style.display = "none";
element = document.getElementById("organsSecondaryDisplayRenderer");
element.style.display = "block";
element = document.getElementById("CheckboxTree");
element.style.display = "none";
}
}
/**
* Change some of the ZincGeometry property for never map geometry
* @callback
*/
var _addNerveMapGeometryCallback = function(GroupName) {
return function(geometry) {
geometry.groupName = GroupName;
if (imageCombiner && geometry.morph && geometry.morph.material.map) {
geometry.morph.material.map = new THREE.Texture(imageCombiner.getCombinedImage());
geometry.morph.material.map.needsUpdate = true;
geometry.morph.material.needsUpdate = true;
}
}
}
/**
* Read in the nerve map models onto the primary renderer when nerve map has been
* toggled on.
*/
var setupNerveMapPrimaryRenderer = function() {
var sceneName = currentName + "_nervemap";
nerveMapScene = organsRenderer.getSceneByName(sceneName);
if (nerveMapScene == undefined) {
var downloadPath = modelsLoader.getOrgansDirectoryPrefix() + "/" + nerveMap.threed.meta;
nerveMapScene = organsRenderer.createScene(sceneName);
nerveMapScene.loadMetadataURL(downloadPath, _addNerveMapGeometryCallback("threed"));
if (nerveMap.threed.view !== undefined)
nerveMapScene.loadViewURL(modelsLoader.getOrgansDirectoryPrefix() + "/" + nerveMap.threed.view);
else {
nerveMapScene.loadViewURL(modelsLoader.getBodyDirectoryPrefix() + "/body_view.json");
}
nerveMapScene.ambient.intensity = 8.0;
nerveMapScene.directionalLight.intensity = 0;
var zincCameraControl = nerveMapScene.getZincCameraControls();
zincCameraControl.setMouseButtonAction("AUXILIARY", "ZOOM");
zincCameraControl.setMouseButtonAction("SECONDARY", "PAN");
nerveMap.additionalReader = new PJP.VaryingTexCoordsReader(nerveMapScene);
var urlsArray = [ modelsLoader.getOrgansDirectoryPrefix() + "/digestive/stomach/nerve_map/3d/xi1_time_0.json",
modelsLoader.getOrgansDirectoryPrefix() + "/digestive/stomach/nerve_map/3d/xi1_time_1.json",
modelsLoader.getOrgansDirectoryPrefix() + "/digestive/stomach/nerve_map/3d/xi0_time_0.json"];
nerveMap.additionalReader.loadURLsIntoBufferGeometry(urlsArray);
}
organsRenderer.setCurrentScene(nerveMapScene);
}
/**
* Load the appropriate svg diagram to the svg viewer on the organs panel.
*/
var setupOrgansNerveSVG = function() {
var svgObject = document.getElementById("organSVG");
if (nerveMap["svg"]["url"]) {
svgObject.setAttribute('data', nerveMap["svg"]["url"] );
}
}
/**
* Create/Get the secondary renderer and display relavant models on it.
*/
var setupSecondaryRenderer = function(species) {
if (secondaryRenderer == null)
secondaryRenderer = PJP.setupRenderer("organsSecondaryDisplayRenderer");
var sceneName = currentSpecies + "/" + currentName;
secondaryScene = secondaryRenderer.getSceneByName(sceneName);
if (secondaryScene == undefined) {
var details = organsFileMap[species][currentSystem][currentPart];
secondaryScene = secondaryRenderer.createScene(sceneName);
secondaryScene.loadMetadataURL(modelsLoader.getOrgansDirectoryPrefix() + "/" +details.meta);
if (details.view !== undefined)
secondaryScene.loadViewURL(modelsLoader.getOrgansDirectoryPrefix() + "/" + details.view);
else {
secondaryScene.loadViewURL(modelsLoader.getBodyDirectoryPrefix() + "/body_view.json");
}
secondaryScene.directionalLight.intensity = 1.4;
var zincCameraControl = secondaryScene.getZincCameraControls();
zincCameraControl.setMouseButtonAction("AUXILIARY", "ZOOM");
zincCameraControl.setMouseButtonAction("SECONDARY", "PAN");
}
secondaryRenderer.setCurrentScene(secondaryScene);
secondaryRenderer.animate();
}
var setTextureForGeometryCallback = function(texture) {
return function(geometry) {
if (geometry.morph && geometry.morph.material.map) {
geometry.morph.material.map = texture;
geometry.morph.material.needsUpdate = true;
texture.needsUpdate = true;
}
}
}
var setTextureForScene = function(targetScene, bitmap) {
if (targetScene) {
var texture = new THREE.Texture(bitmap);
targetScene.forEachGeometry(setTextureForGeometryCallback(texture));
if (nerveMap)
nerveMap.additionalReader.setTexture(texture);
}
}
var activateAdditionalNerveMapRenderer = function() {
updateLayout();
setupOrgansNerveSVG();
}
/**
* Nerve map has been toggled on/off, change organs renderer layput.
*/
var changeNerveMapVisibility = function() {
nerveMapIsActive = !nerveMapIsActive;
if (nerveMapIsActive)
setupNerveMapPrimaryRenderer();
else {
organsRenderer.setCurrentScene(displayScene);
}
activateAdditionalNerveMapRenderer();
}
var organsBackGroundChanged = function() {
return function(value) {
var redValue = parseInt(value[0]);
var greenValue = parseInt(value[1]);
var blueValue = parseInt(value[2]);
var backgroundColourString = 'rgb(' + redValue + ',' + greenValue + ',' + blueValue + ')';
document.getElementById("organsSecondaryDisplayArea").style.backgroundColor = backgroundColourString;
var colour = new THREE.Color(backgroundColourString);
var internalRenderer = organsRenderer.getThreeJSRenderer();
internalRenderer.setClearColor( colour, 1 );
if (secondaryRenderer) {
internalRenderer = secondaryRenderer.getThreeJSRenderer();
internalRenderer.setClearColor( colour, 1 );
}
}
}
var expandCollapseOrgans = function(source, portName) {
if (source.value == "Expand") {
fullScreen = true;
} else {
fullScreen = false;
}
expandCollapse(source, portName);
activateAdditionalNerveMapRenderer();
}
/**
* Initialise organs panel, setup primary renderer and dat.gui UI.
*/
var initialiseOrgansVisualisation = function() {
organsRenderer = PJP.setupRenderer("organsDisplayArea");
organsRenderer.addPreRenderCallbackFunction(updateTimeSliderCallback());
defaultScene = organsRenderer.getCurrentScene();
organGui = new dat.GUI({autoPlace: false});
organGui.domElement.id = 'gui';
organGui.close();
var control = new organsControl();
var controller = organGui.addColor(control, 'Background');
controller.onChange(organsBackGroundChanged());
var customContainer = document.getElementById("organGui").append(organGui.domElement);
var resetViewButton = { 'Reset View':function(){ organsRenderer.resetView() }};
var viewAllButton = { 'View All':function(){ organsRenderer.viewAll() }};
organGuiControls.Speed = 500.0;
speedSlider = organGui.add(organGuiControls, 'Speed', 0, 5000).step(50).onChange(speedSliderChanged());
organGui.add(resetViewButton, 'Reset View');
organGui.add(viewAllButton, 'View All');
organPartsGui = organGui.addFolder('Visibility Control');
organPartsGui.open();
organsRenderer.animate();
}
var imgZoom = function() {
var cssRule = findCSSRule("Basic styles", ".organsImg");
var zoom = currentImgZoom * 100 + "%";
cssRule.style["max-height"] = zoom;
cssRule.style["max-width"] = zoom;
}
var imgZoomIn = function(ratio) {
currentImgZoom = currentImgZoom + ratio;
imgZoom();
}
var imgZoomOut = function(ratio) {
currentImgZoom = currentImgZoom - ratio;
imgZoom();
}
var resetZoom = function() {
currentImgZoom = 1.0;
imgZoom();
}
/**
* Trigger zoom event on the nerve map composite image.
* @callback
*/
var onImageScrollEvent = function(event) {
console.log(event)
if (event.deltaY > 0) {
imgZoomIn(0.1);
} else if (event.deltaY < 0) {
imgZoomOut(0.1);
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
}
function onImageMouseDown( event ) {
if (event.button == 2) {
imgRightClickDown = true;
event.preventDefault();
event.stopImmediatePropagation();
} else {
imgRightClickDown = false;
}
}
function onImageMouseMove( event ) {
if (imgRightClickDown == true) {
targetElement = document.getElementById("organsImgContainer");
targetElement.scrollTop += event.movementY;
targetElement.scrollLeft -= event.movementX;
}
}
function onImageMouseUp( event ) {
if (event.button == 2) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
}
imgRightClickDown = false;
}
function onImageMouseLeave( event ) {
imgRightClickDown = false;
}
var enableImageMouseInteraction = function(targetElement) {
if (targetElement.addEventListener) {
targetElement.addEventListener( 'mousedown', onImageMouseDown, false );
targetElement.addEventListener( 'mousemove', onImageMouseMove, false );
targetElement.addEventListener( 'mouseup', onImageMouseUp, false );
targetElement.addEventListener( 'mouseleave', onImageMouseLeave, false );
targetElement.oncontextmenu = function() { return false;};
targetElement.addEventListener( 'wheel', function ( event ) { onImageScrollEvent(event); }, false);
}
}
/**
* Add UI callbacks after html page has been loaded.
*/
var addUICallback = function() {
var organLinkeButton = document.getElementById("organLinkButton");
organLinkeButton.onclick = function() { openOrganModelLink() };
var organsScreenButton = document.getElementById("organsScreenButton");
organsScreenButton.onclick = function() { expandCollapseOrgans(organsScreenButton, 'organsDisplayPort') };
timeSlider = document.getElementById("organ_animation_slider");
timeSlider.oninput= function() { timeSliderChanged() };
texSlider = document.getElementById("texSlider");
texSlider.oninput= function() { texSliderChanged() };
var organsPlayToggle = document.getElementById("organsPlayToggle");
organsPlayToggle.onclick = function() { playPauseAnimation(organsPlayToggle) };
var element = document.getElementById("organsImgContainer");
enableImageMouseInteraction(element);
}
var loadHTMLComplete = function(link) {
return function(event) {
var localDOM = document.getElementById(PanelName);
var childNodes = null;
if (link.import.body !== undefined)
childNodes = link.import.body.childNodes;
else if (link.childNodes !== undefined)
childNodes = link.childNodes;
for (i = 0; i < childNodes.length; i++) {
localDOM.appendChild(childNodes[i]);
}
addUICallback();
initialiseOrgansVisualisation();
document.head.removeChild(link);
UIIsReady = true;
}
}
/**
* initialise loading of the html layout for the organs panel,
* this is called when the {@link PJP.OrgansViewer} is created.
*
* @async
*/
var initialise = function() {
var link = document.createElement('link');
link.rel = 'import';
link.href = 'snippets/organsViewer.html';
link.onload = loadHTMLComplete(link);
link.onerror = loadHTMLComplete(link);
document.head.appendChild(link);
}
var getOrganDetails = function(speciesName, systemName, partName) {
if (speciesName && systemName && partName) {
if (organsFileMap.hasOwnProperty(speciesName) &&
organsFileMap[speciesName].hasOwnProperty(systemName) &&
organsFileMap[speciesName][systemName].hasOwnProperty(partName))
return organsFileMap[speciesName][systemName][partName];
}
return undefined;
}
/**
* New organs geometry has been added to the scene, add UIs and make sure
* the viewport is correct.
*/
var _addOrganPartCallback = function(systemName, partName, useDefautColour) {
return function(geometry) {
if (geometry.groupName) {
if (!organPartGuiControls.hasOwnProperty(geometry.groupName)) {
organPartGuiControls[geometry.groupName] = true;
organPartsGui.add(organPartGuiControls, geometry.groupName).onChange(changeOrganPartsVisibilityCallback(geometry.groupName));
}
if (useDefautColour)
modelsLoader.setGeometryColour(geometry, systemName, partName);
var organDetails = getOrganDetails(currentSpecies, systemName, partName);
if (organDetails === undefined || organDetails.view == undefined)
{
displayScene.viewAll();
var zincCameraControl = displayScene.getZincCameraControls();
var viewport = zincCameraControl.getCurrentViewport();
zincCameraControl.setDefaultCameraSettings(viewport);
displayScene.resetView();
}
}
}
}
/**
* Toggle data field displays. Data fields displays flow/pressure and other activities of the
* organs.
*/
var toggleFieldVisibility = function(dataFields) {
return function(value) {
for ( var i = 0; i < dataFields.length; i ++ ) {
if (value != i) {
var geometryName = dataFields[i].PartName;
changeOrganPartsVisibility(geometryName, false);
}
}
if (value > -1) {
var partName = dataFields[value].PartName;
if ((displayScene.findGeometriesWithGroupName(partName).length > 0) ||
(displayScene.findGlyphsetsWithGroupName(partName).length > 0)) {
changeOrganPartsVisibility(partName, true);
} else {
var partDetails = getOrganDetails(dataFields[value].SystemName, partName);
if (partDetails != undefined) {
displayScene.loadMetadataURL(modelsLoader.getOrgansDirectoryPrefix() + "/" + partDetails.meta);
}
}
}
}
}
/**
* Return an array containing name(s) of species that also contains the currently displayed organs.
* @returns {Array} containing species name
*/
var getAvailableSpecies = function() {
var availableSpecies = new Array();
availableSpecies.push("none");
var keysArray = Object.keys(organsFileMap);
for (index in keysArray) {
var species = keysArray[index];
if (species != currentSpecies) {
if (organsFileMap[species].hasOwnProperty(currentSystem) &&
organsFileMap[species][currentSystem].hasOwnProperty(currentPart)) {
availableSpecies.push(species);
}
}
}
return availableSpecies;
}
var changeComparedSpecies = function(species) {
if (species == "none") {
comparedSceneIsOn = false;
updateLayout();
} else{
nerveMapIsActive = false;
comparedSceneIsOn = true;
updateLayout();
setupSecondaryRenderer(species);
}
}
/**
* Reset dat.gui ui and also update it to fit the current displaying
* organs.
*/
var updateOrganSpecificGui = function() {
organPartGuiControls = function() {
};
organGui.removeFolder('Visibility Control');
organPartsGui = organGui.addFolder('Visibility Control');
organPartsGui.open();
if (associateData) {
organPartGuiControls["Data Geometry"] = false;
organPartsGui.add(organPartGuiControls, "Data Geometry").onChange(changeDataGeometryVisibility());
}
if (dataFields) {
organPartGuiControls.Field = -1;
var fieldPairs = {};
fieldPairs["None"] = -1;
for ( var i = 0; i < dataFields.length; i ++ ) {
fieldPairs[dataFields[i].PartName] = i;
}
organPartsGui.add(organPartGuiControls, 'Field', fieldPairs ).onChange(toggleFieldVisibility(dataFields));
}
if (nerveMap) {
var nerveMapButton = { 'Toggle nerve':function(){ changeNerveMapVisibility() }};
organPartsGui.add(nerveMapButton, 'Toggle nerve');
}
var otherSpecies = getAvailableSpecies();
if (otherSpecies.length > 1) {
organPartGuiControls["Compared With"] = "none";
var comparedSelected = organPartsGui.add(organPartGuiControls, 'Compared With', otherSpecies);
comparedSelected.onChange(function(species) {
changeComparedSpecies(species);
} );
}
var element = document.getElementById("texSlider");
element.style.display = "none";
}
/**
* Load organ(s) with the provided species, system and part. This will update
* the UIs, load in the models.
* If models are not available, it will attempt to display the data instead (same
* geometry as shown on the {@link PJP.BodyViewer}
*
* @param {String} speciesName
* @param {String} systemName
* @param {String} systemName
* @async
*/
this.loadOrgans = function(speciesName, systemName, partName) {
//Do the work now if UI is ready otherwise try again later with a timeout setup.
if (UIIsReady) {
if (speciesName && systemName && partName) {
resetZoom();
currentSpecies = speciesName;
currentSystem = systemName;
currentPart = partName;
nerveMapIsActive = false;
comparedSceneIsOn = false;
var systemMeta = modelsLoader.getSystemMeta(currentSpecies);
var metaItem = systemMeta[systemName][partName];
// This is used as title
var name = speciesName + "/" + systemName + "/" + partName;
currentName = name;
//Get informations from the array
var organsDetails = getOrganDetails(currentSpecies, systemName, partName);
associateData = undefined;
dataFields = undefined;
externalOrganLink = undefined;
nerveMap = undefined;
if (organsDetails !== undefined){
if (organsDetails.sceneName !== undefined)
name = speciesName + "/" + organsDetails.sceneName;
associateData = organsDetails.associateData;
if (organsDetails.fields)
dataFields = organsDetails.fields;
externalOrganLink = organsDetails.externalLink;
nerveMap = organsDetails.nerveMap;
}
var button = document.getElementById("organLinkButton");
if (externalOrganLink) {
button.style.visibility = "visible";
} else {
button.style.visibility = "hidden";
}
var organScene = organsRenderer.getSceneByName(name);
// Check if organ scene exist,
// Exist: Set it as current scene and update the gui.
// Not: Create a new scene
if (organScene == undefined) {
updateOrganSpecificGui();
organScene = organsRenderer.createScene(name);
displayScene = organScene;
var directionalLight = organScene.directionalLight;
directionalLight.intensity = 1.4;
// Models with the same name exists, read in the models.
if (organsDetails != undefined) {
//Use organs specific viewports if it exists, otherwise use the default viewport.
if (organsDetails.view !== undefined)
organScene.loadViewURL(modelsLoader.getOrgansDirectoryPrefix() + "/" + organsDetails.view);
else {
organScene.loadViewURL(modelsLoader.getBodyDirectoryPrefix() + "/body_view.json");
}
organScene.loadMetadataURL(modelsLoader.getOrgansDirectoryPrefix() + "/" + organsDetails.meta,
_addOrganPartCallback(systemName, partName, false));
var zincCameraControl = organScene.getZincCameraControls();
zincCameraControl.setMouseButtonAction("AUXILIARY", "ZOOM");
zincCameraControl.setMouseButtonAction("SECONDARY", "PAN");
//Create a picker scene if it exists.
if (organsDetails.picker != undefined) {
var pickerSceneName = name + "_picker_scene";
pickerScene = organsRenderer.createScene(pickerSceneName);
pickerScene.loadMetadataURL(modelsLoader.getOrgansDirectoryPrefix() + "/" + organsDetails.picker);
zincCameraControl.enableRaycaster(pickerScene, _pickingCallback(), _hoverCallback());
} else {
zincCameraControl.enableRaycaster(organScene, _pickingCallback(), _hoverCallback());
}
} else {
organScene.loadViewURL(modelsLoader.getBodyDirectoryPrefix() + "/body_view.json");
var downloadPath = metaItem["BodyURL"];
if (metaItem["FileFormat"] == "JSON") {
organScewith_body.htmlne.loadMetadataURL(downloadPath, _addOrganPartCallback(systemName, partName, false));
}
else if (metaItem["FileFormat"] == "STL")
organScene.loadSTL(downloadPath, partName, _addOrganPartCallback(systemName, partName, true));
else if (metaItem["FileFormat"] == "OBJ")
organScene.loadOBJ(downloadPath, partName, _addOrganPartCallback(systemName, partName, true));
organsRenderer.setCurrentScene(organScene);
var zincCameraControl = organScene.getZincCameraControls();
zincCameraControl.enableRaycaster(organScene, _pickingCallback(), _hoverCallback());
zincCameraControl.setMouseButtonAction("AUXILIARY", "ZOOM");
zincCameraControl.setMouseButtonAction("SECONDARY", "PAN");
}
var directionalLight = organScene.directionalLight;
directionalLight.intensity = 1.4;
organsRenderer.setCurrentScene(organScene);
} else if (displayScene != organScene){
updateOrganSpecificGui();
organsRenderer.setCurrentScene(organScene);
displayScene = organScene;
var pickerSceneName = name + "_picker_scene";
pickerScene = organsRenderer.getSceneByName(pickerSceneName);
displayScene.forEachGeometry(_addOrganPartCallback());
displayScene.forEachGlyphset(_addOrganPartCallback());
}
setOrgansPanelTitle(name);
updateTimeSlider();
updateSpeedSlider();
}
} else {
if (timeoutID == 0)
timeoutID = setTimeout(loadOrgansTimeoutCallback(speciesName, systemName, partName), 500);
}
}
var loadOrgansTimeoutCallback = function(speciesName, systemName, partName) {
return function () {
timeoutID = 0;
_this.loadOrgans(speciesName, systemName, partName);
}
}
var triggerAnimation = function() {
if (organsRenderer.playAnimation == true) {
organsRenderer.playAnimation = false;
} else {
organsRenderer.playAnimation = true;
}
}
var resetView = function()
{
organsRenderer.resetView();
}
var viewAll = function()
{
organsRenderer.viewAll();
}
var openOrganModelLink = function() {
window.open(externalOrganLink, '');
}
initialise();
require([
"dojo/ready",
"dojo/dom",
"cbtree/Tree", // Checkbox tree
"cbtree/model/TreeStoreModel", // Object Store Forest Model
"cbtree/store/ObjectStore" // Object Store with Hierarchy
], function( ready, dom, Tree, ObjectStoreModel, ObjectStore) {
var store = new ObjectStore( { url:"models/organsViewerModels/digestive/stomach/store/neuritemap.json", idProperty:"id"});
var model = new ObjectStoreModel( { store: store,
query: {name: "Root"},
rootLabel: "All",
checkedRoot: true
});
var imgEle = {};
var imgPrefix = "models/organsViewerModels/digestive/stomach/nerve_map/texture/";
var tree = undefined;
var imageUpdated = false;
function modelItemChanged(item, propertyName, newValue, oldValue) {
if (item.img) {
if (propertyName == "checked") {
var elem = imgEle[item.id];
if (newValue == false) {
elem.style.display = "none";
imageCombiner.removeElement(elem);
} else {
elem.style.display = "inline";
imageCombiner.addElement(elem);
}
imageUpdated = true;
}
}
}
function checkBoxClicked(item, node, event) {
if (imageUpdated) {
var bitmap = imageCombiner.getCombinedImage();
setTextureForScene(nerveMapScene, bitmap);
imageUpdated = false;
}
}
function forEachChildrenCreateImageElements(container) {
return function (childrenArray) {
for (var i = 0; i < childrenArray.length; i++) {
var currentItem = childrenArray[i];
if (currentItem.img) {
var imgURL = imgPrefix + currentItem.img;
var elem = document.createElement("img");
elem.className = "organsImg";
elem.src = imgURL;
elem.style.display = "none";
container.appendChild(elem);
imgEle[currentItem.id] = elem;
if (currentItem.checked == true) {
elem.style.display = "inline";
imageCombiner.addElement(elem);
}
}
if (currentItem.colour) {
if (tree._itemNodesMap[currentItem.id]) {
var nodeElem = tree._itemNodesMap[currentItem.id][0].labelNode;
if (nodeElem) {
nodeElem.style.color = "#" + currentItem.colour;
}
}
}
model.getChildren(currentItem, forEachChildrenCreateImageElements(container));
}
}
}
/**
* Setup the tree for toggling parts of the nerve map texture/image.
* Uses dojo tree and checkbox for the UI and ImageCombiner for image composition.
*/
function rootIsReady() {
return function(root) {
var height = document.getElementById("organsRootImage").height;
var width = document.getElementById("organsRootImage").width;
imageCombiner = new ImageCombiner();
imageCombiner.setSize(width, height);
imageCombiner.addElement(document.getElementById("organsRootImage"));
var container = dom.byId("organsImgContainer");
model.getChildren(root, forEachChildrenCreateImageElements(container));
var bitmap = imageCombiner.getCombinedImage();
setTextureForScene(nerveMapScene, bitmap);
}
}
ready( function () {
tree = new Tree( { model: model,
id: "neuritemap",
autoExpand:true,
branchIcons:false,
leafIcons: false,
} );
tree.domNode.style.height = "100%";
tree.domNode.style.fontSize = "70%";
model.getRoot(rootIsReady());
model.on("change", modelItemChanged);
tree.on("checkBoxClick", checkBoxClicked);
tree.placeAt( "CheckboxTree" );
tree.startup();
});
});
}