const THREE = require('three');
const MarkerCluster = require('./primitives/markerCluster').MarkerCluster;
const SceneLoader = require('./sceneLoader').SceneLoader;
const SceneExporter = require('./sceneExporter').SceneExporter;
const Viewport = require('./controls').Viewport;
const createBufferGeometry = require('./utilities').createBufferGeometry;
const getCircularTexture = require('./utilities').getCircularTexture;
let uniqueiId = 0;
const getUniqueId = function () {
return "sc" + uniqueiId++;
}
const defaultMetadata = function() {
return {
Duration: "6 secs",
OriginalDuration: "-",
TimeStamps: {}
}
};
const defaultDuration = 6000;
/**
* A Scene contains {@link Region},and
* {@link CameraControls} which controls the viewport and additional features.
* It is the main object used for controlling what is and what is not displayed
* on the renderer.
*
* @class
* @param {Object} containerIn - Container to create the renderer on.
* @author Alan Wu
* @return {Scene}
*/
exports.Scene = function (containerIn, rendererIn) {
const container = containerIn;
let videoHandler = undefined;
let sceneLoader = new SceneLoader(this);
let minimap = undefined;
let zincObjectAddedCallbacks = {};
let zincObjectAddedCallbacks_id = 0;
let zincObjectRemovedCallbacks = {};
let zincObjectRemovedCallbacks_id = 0;
const scene = new THREE.Scene();
const rootRegion = new (require('./region').Region)(undefined, this);
scene.add(rootRegion.getGroup());
const tempGroup = new THREE.Group();
scene.add(tempGroup);
/**
* A {@link THREE.DirectionalLight} object for controlling lighting of this scene.
*/
this.directionalLight = undefined;
/**
* a {@link THREE.AmbientLight} for controlling the ambient lighting of this scene.
*/
this.ambient = undefined;
this.camera = undefined;
let duration = 6000;
let zincCameraControls = undefined;
this.sceneName = undefined;
let stereoEffectFlag = false;
let stereoEffect = undefined;
this.autoClearFlag = true;
this.displayMarkers = false;
this.displayMinimap = false;
this.minimapScissor = {
x_offset: 16,
y_offset: 16,
width: 128,
height: 128,
align: "top-left",
updateRequired: true
};
let scissor = {x: 0, y: 0};
let metadata = defaultMetadata();
let _markerTarget = new THREE.Vector2();
let pickableObjectsList = [];
this.forcePickableObjectsUpdate = false;
this.uuid = getUniqueId();
let markerCluster = new MarkerCluster(this);
markerCluster.disable();
scene.add(markerCluster.group);
const getDrawingWidth = () => {
if (container)
if (typeof container.clientWidth !== "undefined")
return container.clientWidth;
else
return container.width;
return 0;
}
const getDrawingHeight = () => {
if (container)
if (typeof container.clientHeight !== "undefined")
return container.clientHeight;
else
return container.height;
return 0;
}
/**
* 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 = () => {
return sceneLoader.getDownloadProgress();
}
//called from Renderer when panel has been resized
this.onWindowResize = () => {
const wHeight = getDrawingHeight();
this.camera.aspect = getDrawingWidth() / wHeight;
this.camera.updateProjectionMatrix();
this.minimapScissor.updateRequired = true;
zincCameraControls.onResize();
zincCameraControls.calculateHeightPerPixelAtZeroDepth(wHeight);
}
/**
* Reset the viewport of this scene to its original state.
*/
this.resetView = () => {
this.onWindowResize();
zincCameraControls.resetView();
}
/**
* Set the zoom level by unit scroll rate
*/
this.changeZoomByScrollRateUnit = unit => {
zincCameraControls.changeZoomByScrollRateUnit(unit);
}
//Setup the camera for this scene, it also initialise the lighting
const setupCamera = () => {
this.camera = new THREE.PerspectiveCamera(40, getDrawingWidth() / getDrawingHeight(), 0.0, 10.0);
this.ambient = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(this.ambient);
this.directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
scene.add(this.directionalLight);
zincCameraControls = new (require('./controls').CameraControls)(this.camera, rendererIn.domElement, rendererIn, this);
zincCameraControls.setDirectionalLight(this.directionalLight);
zincCameraControls.resetView();
minimap = new (require('./minimap').Minimap)(this);
};
setupCamera();
/**
* Load the viewport Data from the argument {@link Zinc.Viewport} and set it as
* the default viewport of this scene.
*
* @param {Zinc.Viewport} viewData - Viewport data to be loaded.
*/
this.loadView = settings => {
const viewPort = new Viewport();
viewPort.setFromObject(settings);
zincCameraControls.setCurrentCameraSettings(viewPort);
return true;
}
/**
* Set up multiple views.
*
* @param {Zinc.Viewport} viewData - Viewport data to be loaded.
*/
this.setupMultipleViews = (defaultView, entries) => {
for (const [name, settings] of Object.entries(entries)) {
const viewport = new Viewport();
viewport.setFromObject(settings);
zincCameraControls.addViewport(name, viewport);
}
zincCameraControls.setDefaultViewport(defaultView);
}
/**
* Get the bounding box of all the object in this scene only.
*
* @returns {THREE.Box3}
*/
this.getBoundingBox = () => {
return rootRegion.getBoundingBox(true);
}
/**
* Adjust the viewport to display the desired volume provided by the bounding box.
*
* @param {THREE.Box3} boundingBox - The bounding box which describes the volume of
* which we the viewport should be displaying.
*/
this.viewAllWithBoundingBox = boundingBox => {
if (boundingBox) {
const viewport = zincCameraControls.getViewportFromBoundingBox(boundingBox, 1.0);
zincCameraControls.setCurrentCameraSettings(viewport);
zincCameraControls.calculateHeightPerPixelAtZeroDepth(getDrawingHeight());
markerCluster.markerUpdateRequired = true;
}
}
/**
* Adjust zoom distance to include all primitives in scene only.
*/
this.viewAll = () => {
const boundingBox = this.getBoundingBox();
this.viewAllWithBoundingBox(boundingBox);
markerCluster.markerUpdateRequired = true;
}
/**
* A function which iterates through the list of geometries and call the callback
* function with the geometries as the argument.
* @param {Function} callbackFunction - Callback function with the geometry
* as an argument.
*/
this.forEachGeometry = callbackFunction => {
rootRegion.forEachGeometry(callbackFunction, true);
}
/**
* A function which iterates through the list of glyphsets and call the callback
* function with the glyphset as the argument.
* @param {Function} callbackFunction - Callback function with the glyphset
* as an argument.
*/
this.forEachGlyphset = callbackFunction => {
rootRegion.forEachGlyphset(callbackFunction, true);
}
/**
* A function which iterates through the list of pointsets and call the callback
* function with the pointset as the argument.
* @param {Function} callbackFunction - Callback function with the pointset
* as an argument.
*/
this.forEachPointset = callbackFunction => {
rootRegion.forEachPointset(callbackFunction, true);
}
/**
* A function which iterates through the list of lines and call the callback
* function with the lines as the argument.
* @param {Function} callbackFunction - Callback function with the lines
* as an argument.
*/
this.forEachLine = callbackFunction => {
rootRegion.forEachLine(callbackFunction, true);
}
/**
* Find and return all geometries in this scene with the matching GroupName.
*
* @param {String} GroupName - Groupname to match with.
* @returns {Array}
*/
this.findGeometriesWithGroupName = GroupName => {
return rootRegion.findGeometriesWithGroupName(GroupName, true);
}
/**
* Find and return all pointsets in this scene with the matching GroupName.
*
* @param {String} GroupName - Groupname to match with.
* @returns {Array}
*/
this.findPointsetsWithGroupName = GroupName => {
return rootRegion.findPointsetsWithGroupName(GroupName, true);
}
/**
* Find and return all glyphsets in this scene with the matching GroupName.
*
* @param {String} GroupName - Groupname to match with.
* @returns {Array}
*/
this.findGlyphsetsWithGroupName = GroupName => {
return rootRegion.findGlyphsetsWithGroupName(GroupName, true);
}
/**
* Find and return all lines in this scene with the matching GroupName.
*
* @param {String} GroupName - Groupname to match with.
* @returns {Array}
*/
this.findLinesWithGroupName = GroupName => {
return rootRegion.findLinesWithGroupName(GroupName, true);
}
/**
* Find a list of objects with the specified name, this will
* tranverse through the region tree to find all child objects
* with matching name.
*
* @param {String} GroupName - Groupname to match with.
* @returns {Array}
*/
this.findObjectsWithGroupName = GroupName => {
return rootRegion.findObjectsWithGroupName(GroupName, true);
}
this.findObjectsWithAnatomicalId = anatomicalId => {
return rootRegion.findObjectsWithAnatomicalId(anatomicalId, true);
}
/**
* Get the bounding box of all zinc objects in list.
*
* @param {Array} objectsArray - Groupname to match with.
* @returns {THREE.Box3}
*/
this.getBoundingBoxOfZincObjects = objectsArray => {
let boundingBox = undefined;
for (let i = 0; i < objectsArray.length; i++) {
let box = objectsArray[i].getBoundingBox();
if (box) {
if (!boundingBox)
boundingBox = box;
else
boundingBox.union(box);
}
}
return boundingBox;
}
/**
* Convert the vector3 into screen coordinates.
*
* @param {THREE.Vector3} point - Vector 3 containing the point to convert,
* this vector will be overwritten with the returned value.
* @param {Array} objectsArray - Groupname to match with.
* @returns {THREE.Vector3}
*/
this.vectorToScreenXY = point => {
point.project(this.camera);
let width = getDrawingWidth();
let height = getDrawingHeight();
let widthHalf = (width / 2);
let heightHalf = (height / 2);
point.x = (point.x * widthHalf) + widthHalf;
point.y = - (point.y * heightHalf) + heightHalf;
return point;
}
/**
* Get the screen coordinate of the centroid of provided list of objects.
*
* @param {Array} zincObjects - List of {@link ZincObject}.
* @returns {THREE.Vector3}
*/
this.getObjectsScreenXY = zincObjects => {
if (zincObjects && zincObjects.length > 0) {
let boundingBox = this.getBoundingBoxOfZincObjects(zincObjects);
const center = new THREE.Vector3();
boundingBox.getCenter(center);
return this.vectorToScreenXY(center);
}
return undefined;
}
/**
* Get the screen coordinate of the centroid of all objects
* in scene with the provided name.
*
* @param {String} name - List of {@link ZincObject}.
* @returns {THREE.Vector3}
*/
this.getNamedObjectsScreenXY = name => {
let zincObjects = this.findObjectsWithGroupName(name);
return this.getObjectsScreenXY(zincObjects);
};
/**
* Add zinc object into the root {@link Region} of sfcene.
*
* @param {ZincObject} - zinc object ot be added.
* @returns {THREE.Vector3}
*/
this.addZincObject = zincObject => {
if (zincObject) {
rootRegion.addZincObject(zincObject);
if (zincCameraControls)
zincCameraControls.calculateMaxAllowedDistance(this);
}
}
/**
* 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 = (metaurl, glyphurl, groupName, finishCallback) => {
sceneLoader.loadGlyphsetURL(rootRegion, metaurl, glyphurl, groupName, finishCallback);
}
/**
* Load a pointset into this scene object.
*
* @param {String} metaurl - Provide informations such as transformations, colours
* and others for each of the glyph in the glyphsset.
* @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 = (url, timeEnabled, morphColour, groupName, finishCallback) => {
sceneLoader.loadPointsetURL(rootRegion, url, timeEnabled, morphColour, groupName, finishCallback);
}
/**
* Load lines into this scene object.
*
* @param {String} metaurl - Provide informations such as transformations, colours
* and others for each of the glyph in the glyphsset.
* @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 = (url, timeEnabled, morphColour, groupName, finishCallback) => {
sceneLoader.loadLinesURL(rootRegion, url, timeEnabled, morphColour, groupName, finishCallback);
}
/**
* 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 = (url, groupName, finishCallback) => {
sceneLoader.loadSTL(rootRegion, url, groupName, 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 = (url, groupName, finishCallback) => {
sceneLoader.loadOBJ(rootRegion, url, groupName, finishCallback);
}
/**
* 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 = (url, finishCallback, allCompletedCallback) => {
sceneLoader.loadMetadataURL(rootRegion, url, finishCallback, allCompletedCallback);
}
/**
* 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 = (urls, colours, opacities, timeEnabled, morphColour, finishCallback) => {
sceneLoader.loadModelsURL(rootRegion. urls, colours, opacities, timeEnabled, morphColour, finishCallback);
}
/**
* Load the viewport from an external location provided by the url.
* @param {String} URL - address to the file containing viewport information.
*/
this.loadViewURL = url => {
sceneLoader.loadViewURL(url);
}
/**
* 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.Scene#loadMetadataURL} instead.
*
* @param {String} URL - address to the file containing viewport and model information.
* @deprecated
*/
this.loadFromViewURL = (jsonFilePrefix, finishCallback) => {
sceneLoader.loadFromViewURL(jsonFilePrefix, finishCallback);
}
/**
* Load GLTF into this scene object.
*/
this.loadGLTF = (url, finishCallback, allCompletedCallback, options) => {
sceneLoader.loadGLTF(rootRegion, url, finishCallback, allCompletedCallback, options);
}
//Update the directional light for this scene.
this.updateDirectionalLight = () => {
zincCameraControls.updateDirectionalLight();
}
/**
* Add any {THREE.Object} into this scene.
* @param {THREE.Object} object - to be addded into this scene.
*/
this.addObject = object => {
scene.add(object);
}
/**
* Remove any {THREE.Object} from this scene.
* @param {THREE.Object} object - to be removed from this scene.
*/
this.removeObject = object => {
scene.remove(object);
}
/**
* Get the current time of the scene.
* @return {Number}
*/
this.getCurrentTime = () => {
if (videoHandler != undefined) {
return videoHandler.getCurrentTime(duration);
}
const time = rootRegion.getCurrentTime();
if (time !== -1)
return time;
return 0;
}
/**
* Set the current time of all the geometries and glyphsets of this scene.
* @param {Number} time - Value to set the time to.
*/
this.setMorphsTime = (time) => {
if (videoHandler != undefined) {
videoHandler.setMorphTime(time, duration);
}
rootRegion.setMorphTime(time, true);
}
/**
* Check if any object in this scene is time varying.
*
* @return {Boolean}
*/
this.isTimeVarying = () => {
if (videoHandler && videoHandler.video && !videoHandler.video.error) {
return true;
}
return rootRegion.isTimeVarying();
}
/**
* Update geometries and glyphsets based on the calculated time.
* @private
*/
this.renderGeometries = (playRate, delta, playAnimation) => {
// Let video dictates the progress if one is present
let options = {};
options.camera = zincCameraControls;
//Global markers flag, marker can be set at individual zinc object level
//overriding this flag.
options.displayMarkers = this.displayMarkers;
options.markerCluster = markerCluster;
options.markersList = markerCluster.markers;
options.ndcToBeUpdated = false;
//Always set marker cluster update required when playAnimation is true
//to make sure it is updated when it stops
if (playAnimation) {
options.markerCluster.markerUpdateRequired = true;
}
if (videoHandler) {
if (videoHandler.isReadyToPlay()) {
if (playAnimation) {
videoHandler.video.play();
} else {
videoHandler.video.pause();
}
const currentTime = videoHandler.video.currentTime /
videoHandler.getVideoDuration() * duration;
if (0 == sceneLoader.toBeDownloaded) {
zincCameraControls.setTime(currentTime);
options.ndcToBeUpdated = zincCameraControls.update(0);
if (options.ndcToBeUpdated) {
zincCameraControls.calculateHeightPerPixelAtZeroDepth(getDrawingHeight());
}
rootRegion.setMorphTime(currentTime, true);
rootRegion.renderGeometries(0, 0, playAnimation, zincCameraControls, options, true);
} else {
zincCameraControls.update(0);
}
//console.log(videoHandler.video.currentTime / videoHandler.getVideoDuration() * 6000);
} else {
myPlayRate = 0;
}
} else {
if (0 == sceneLoader.toBeDownloaded) {
options.ndcToBeUpdated = zincCameraControls.update(delta);
if (options.ndcToBeUpdated) {
zincCameraControls.calculateHeightPerPixelAtZeroDepth(getDrawingHeight());
}
rootRegion.renderGeometries(playRate, delta, playAnimation, zincCameraControls, options, true);
} else {
zincCameraControls.update(0);
}
}
}
/**
* Return the internal {THREE.Scene}.
* @return {THREE.Scene}
*/
this.getThreeJSScene = () => {
return scene;
}
this.setVideoHandler = (videoHandlerIn) => {
if (!videoHandler)
videoHandler = videoHandlerIn;
}
/**
* Set a group of scenes into this parent scene. This group of
* scenes will also be rendered when this scene is rendered.
* @private
*/
this.setAdditionalScenesGroup = scenesGroup => {
scene.add(scenesGroup);
}
let getWindowsPosition = (align, x_offset, y_offset, width, height,
renderer_width, renderer_height) => {
let x = 0;
let y = 0;
if (align.includes("top")) {
y = renderer_height - height - y_offset;
} else if (align.includes("bottom")) {
y = y_offset;
} else {
y = Math.floor((renderer_height - height) / 2.0);
}
if (align.includes("left")) {
x = x_offset;
} else if (align.includes("right")) {
x = renderer_width - x_offset- width;
} else {
x = Math.floor((renderer_width - width) / 2.0);
}
return {x: x, y: y};
}
const renderMinimap = renderer => {
if (this.displayMinimap === true) {
renderer.setScissorTest(true);
renderer.getSize(_markerTarget);
if (this.minimapScissor.updateRequired) {
scissor = getWindowsPosition(this.minimapScissor.align,
this.minimapScissor.x_offset,
this.minimapScissor.y_offset,
this.minimapScissor.width,
this.minimapScissor.height,
_markerTarget.x, _markerTarget.y);
this.minimapScissor.updateRequired = false;
}
renderer.setScissor(
scissor.x,
scissor.y,
this.minimapScissor.width,
this.minimapScissor.height);
renderer.setViewport(
scissor.x,
scissor.y,
this.minimapScissor.width,
this.minimapScissor.height);
minimap.updateCamera();
scene.add(minimap.mask);
renderer.render(scene, minimap.camera);
scene.remove(minimap.mask);
renderer.setScissorTest(false);
renderer.setViewport(0, 0, _markerTarget.x, _markerTarget.y);
}
}
/**
* Render the scene.
* @private
*/
this.render = renderer => {
if (this.autoClearFlag)
renderer.clear();
if (stereoEffectFlag && stereoEffect) {
stereoEffect.render(scene, this.camera);
} else {
renderer.render(scene, this.camera);
renderMinimap(renderer);
}
}
/**
* Enable or disable interactive control, this is on by default.
*
* @param {Boolean} flag - Indicate either interactive control
* should be enabled or disabled.
*/
this.setInteractiveControlEnable = flag => {
if (flag == true)
zincCameraControls.enable();
else
zincCameraControls.disable();
}
/**
* Get the camera control of this scene.
* @return {Zinc.CameraControls}
*/
this.getZincCameraControls = () => {
return zincCameraControls;
}
/**
* Get the internal {THREE.Scene}.
* @return {THREE.Scene}
*/
this.getThreeJSScene = () => {
return scene;
}
/**
* Set the default duration value for geometries and glyphsets
* that are to be loaded into this scene.
* @param {Number} durationIn - duration of the scene.
*/
this.setDuration = durationIn => {
rootRegion.setDuration(durationIn);
duration = durationIn;
zincCameraControls.setPathDuration(durationIn);
sceneLoader.duration = durationIn;
}
/**
* Get the default duration value.
* @return {Number}
*/
this.getDuration = () => {
return duration;
}
/**
* Enable or disable stereo effect of this scene.
* @param {Boolean} stereoFlag - Indicate either stereo effect control
* should be enabled or disabled.
*/
this.setStereoEffectEnable = stereoFlag => {
if (stereoFlag == true) {
if (!stereoEffect) {
stereoEffect = new require('./controls').StereoEffect(rendererIn);
}
}
rendererIn.setSize(getDrawingWidth(), getDrawingHeight());
this.camera.updateProjectionMatrix();
stereoEffectFlag = stereoFlag;
}
/**
* Check rather object is in scene.
*
* @return {Boolean}
*/
this.objectIsInScene = zincObject => {
return rootRegion.objectIsInRegion(zincObject, true);
}
/**
* Rotate the camera view to view the entirety of the
* bounding box with a smooth transition within the providied
* transitionTime.
*
* @param {THREE.Box3} boundingBox - the bounding box to target
* @param {Number} transitionTime - Duration to perform the transition.
*/
this.alignBoundingBoxToCameraView = (boundingBox, transitionTime) => {
if (boundingBox) {
const center = new THREE.Vector3();
boundingBox.getCenter(center);
const viewport = this.getZincCameraControls().getCurrentViewport();
const target = new THREE.Vector3(viewport.targetPosition[0],
viewport.targetPosition[1], viewport.targetPosition[2]);
const eyePosition = new THREE.Vector3(viewport.eyePosition[0],
viewport.eyePosition[1], viewport.eyePosition[2]);
const newVec1 = new THREE.Vector3();
const newVec2 = new THREE.Vector3();
newVec1.subVectors(target, eyePosition).normalize();
newVec2.subVectors(target, center).normalize();
const newVec3 = new THREE.Vector3();
newVec3.crossVectors(newVec1, newVec2);
const angle = newVec1.angleTo(newVec2);
if (transitionTime > 0) {
this.getZincCameraControls().rotateCameraTransition(newVec3,
angle, transitionTime);
this.getZincCameraControls().enableCameraTransition();
} else {
this.getZincCameraControls().rotateAboutLookAtpoint(newVec3, angle);
}
markerCluster.markerUpdateRequired = true;
}
}
/**
* Translate the camera view to the center of the
* bounding box with a smooth transition within the providied
* transitionTime.
*
* @param {THREE.Box3} boundingBox - the bounding box to target
* @param {Number} transitionTime - Duration to perform the transition.
*/
this.translateBoundingBoxToCameraView = (boundingBox, scaleRadius, transitionTime) => {
if (boundingBox) {
const oldViewport = this.getZincCameraControls().getCurrentViewport();
const viewport = this.getZincCameraControls().getViewportFromBoundingBox(boundingBox, scaleRadius);
if (transitionTime > 0) {
this.getZincCameraControls().cameraTransition(oldViewport,
viewport, transitionTime);
this.getZincCameraControls().enableCameraTransition();
}
markerCluster.markerUpdateRequired = true;
}
}
/**
* Transition the camera into viewing the zinc object with a
* smooth transition within the providied transitionTime.
*
* @param {ZincObject} zincObject - the bounding box to target
* @param {Number} transitionTime - Duration to perform the transition.
*/
this.alignObjectToCameraView = (zincObject, transitionTime) => {
if (this.objectIsInScene(zincObject)) {
const boundingBox = zincObject.getBoundingBox();
this.alignBoundingBoxToCameraView(boundingBox, transitionTime);
}
}
/**
* Set the camera to point to the centroid of the zinc object.
*
* @param {ZincObject} zincObject - the bounding box to target
*/
this.setCameraTargetToObject = zincObject => {
if (this.objectIsInScene(zincObject)) {
const center = new THREE.Vector3();
const boundingBox = zincObject.getBoundingBox();
const viewport = this.getZincCameraControls().getCurrentViewport();
boundingBox.getCenter(center);
const target = new THREE.Vector3(viewport.targetPosition[0],
viewport.targetPosition[1], viewport.targetPosition[2]);
const eyePosition = new THREE.Vector3(viewport.eyePosition[0],
viewport.eyePosition[1], viewport.eyePosition[2]);
const newVec1 = new THREE.Vector3();
const newVec2 = new THREE.Vector3();
newVec1.subVectors(eyePosition, target);
newVec2.addVectors(center, newVec1);
viewport.eyePosition[0] = newVec2.x;
viewport.eyePosition[1] = newVec2.y;
viewport.eyePosition[2] = newVec2.z;
viewport.targetPosition[0] = center.x;
viewport.targetPosition[1] = center.y;
viewport.targetPosition[2] = center.z;
this.getZincCameraControls().setCurrentCameraSettings(viewport);
markerCluster.markerUpdateRequired = true;
}
}
/**
* Check if stereo effect is enabled.
* @returns {Boolean}
*/
this.isStereoEffectEnable = () => {
return stereoEffectFlag;
}
/**
* Remove a ZincObject from this scene if it presents. This will eventually
* destroy the object and free up the memory.
* @param {Zinc.Object} zincObject - object to be removed from this scene.
*/
this.removeZincObject = zincObject => {
rootRegion.removeZincObject(zincObject);
if (zincCameraControls) {
zincCameraControls.calculateMaxAllowedDistance(this);
}
markerCluster.markerUpdateRequired = true;
}
/**
* Update pickable objects list
*/
this.updatePickableThreeJSObjects = () => {
pickableObjectsList.length = 0;
if (markerCluster.isEnabled) {
pickableObjectsList.push(markerCluster.group);
}
rootRegion.getPickableThreeJSObjects(pickableObjectsList, true);
this.forcePickableObjectsUpdate = false;
}
/**
* Get all pickable objects.
*/
this.getPickableThreeJSObjects = () => {
//The list will only be updated if changes have been made
//in region or a flag has been raise
if (this.forcePickableObjectsUpdate ||
rootRegion.checkPickableUpdateRequred(true)) {
this.updatePickableThreeJSObjects();
}
return pickableObjectsList;
}
/**
* Get the Normalised coordinates on minimap if mouse event is
* inside the minimap
*/
this.getNormalisedMinimapCoordinates = (renderer, event) => {
if (this.displayMinimap) {
const target = new THREE.Vector2();
renderer.getSize(target);
let offsetY = target.y - event.clientY;
if (((scissor.x + this.minimapScissor.width) > event.clientX) &&
(event.clientX > scissor.x) &&
((scissor.y + this.minimapScissor.height) > offsetY) &&
(offsetY > scissor.y)) {
let x = ((event.clientX - scissor.x) /
this.minimapScissor.width) * 2.0 - 1.0;
let y = ((offsetY - scissor.y) /
this.minimapScissor.height) * 2.0 - 1.0;
return {"x": x, "y": y};
}
}
return undefined;
}
/**
* Get the coordinates difference of the current viewing
* point and projected coordinates.
*/
this.getMinimapDiffFromNormalised = (x, y) => {
if (minimap)
return minimap.getDiffFromNormalised(x, y);
return undefined;
}
this.isWebGL2 = () => {
return rendererIn.isWebGL2();
}
/**
* Remove all objects that are created with ZincJS APIs and it will free the memory allocated.
* This does not remove obejcts that are added using the addObject APIs.
*/
this.clearAll = () => {
markerCluster.clear();
rootRegion.clear(true);
this.clearZincObjectAddedCallbacks();
this.clearZincObjectRemovedCallbacks();
sceneLoader.toBeDwonloaded = 0;
if (zincCameraControls) {
zincCameraControls.calculateMaxAllowedDistance(this);
}
markerCluster.markerUpdateRequired = true;
}
/**
* All time stamp to the metadata TimeStamps field.
*/
this.addMetadataTimeStamp = (key, time) => {
metadata["TimeStamps"][key] = convertDurationObjectTomSec(time);
}
/**
* Get a specific metadata field.
*/
this.getMetadataTag = key => {
return metadata[key];
}
/**
* Get all metadata set for the scene.
*/
this.getMetadata = () => {
return metadata;
}
/**
* Set a specific metadata field.
*/
this.setMetadataTag = (key, value) => {
metadata[key] = value;
}
/**
* Remove a specific metadata field.
*/
this.removeMetadataTag = key => {
delete metadata[key];
}
/**
* Reset all metadata fields to original value.
*/
this.resetMetadata = () => {
metadata = defaultMetadata();
}
/**
* Reset duration of scene to default value.
*/
this.resetDuration = () => {
this.setDuration(defaultDuration);
}
// Turn the object into a readable string {years: years,months: months,
// weeks: weeks, days: days, hours: hours, mins: mins, secs: secs }
const convertDurationObjectToString = duration => {
return [
...(duration.years ? [`${duration.years}years`] : []),
...(duration.months ? [`${duration.months}months`] : []),
...(duration.weeks ? [`${duration.weeks}weeks`] : []),
...(duration.days ? [`${duration.days}days`] : []),
...(duration.hours ? [`${duration.hours}hours`] : []),
...(duration.mins ? [`${duration.mins}mins`] : []),
...(duration.secs ? [`${duration.secs}secs`] : []),
].join(' ');
}
// Turn the object into a number representing milliesecond {years: years,months: months,
// weeks: weeks, days: days, hours: hours, mins: mins, secs: secs }
const convertDurationObjectTomSec = duration => {
return duration.years ? duration.years * 31536000000 : 0 +
duration.months ? duration.months * 2592000000 : 0 +
duration.weeks ? duration.weeks * 604800000 : 0 +
duration.days ? duration.days * 86400000 : 0 +
duration.hours ? duration.hours * 3600000 : 0 +
duration.mins ? duration.mins * 60000 : 0 +
duration.secs ? duration.secs * 1000 : 0;
}
// Set the readable duration and timer using an object
// with the following format {years: years,months: months, weeks: weeks, days: days,
// hours: hours, mins: mins, secs: secs }
this.setDurationFromObject = duration => {
const string = convertDurationObjectToString(duration);
const millisec = convertDurationObjectTomSec(duration);
this.setMetadataTag("Duration", string);
this.setDuration(millisec);
}
// Set the readable original duration using an object
// with the following format {years: years,months: months, weeks: weeks, days: days,
// hours: hours, mins: mins, secs: secs }
this.setOriginalDurationFromObject = duration => {
const string = convertDurationObjectToString(duration);
this.setMetadataTag("OriginalDuration", string);
}
/**
* Export the scene in GLTF format, it can either return it in
* string or binary form.
*
* @param {Boolean} binary - Indicate it should be exported as binary or
* text.
*
* @return {Promise} The exported data if the promise resolve successfully
*/
this.exportGLTF = (binary) => {
const exporter = new SceneExporter(this);
return exporter.exportGLTF(binary);
}
/**
* Get the root region of the scene.
*
* @return {Region} Return the root region of the scene
*/
this.getRootRegion = () => {
return rootRegion;
}
/**
* Create points in region specified in the path
*
*/
this.createLines = ( regionPath, groupName, coords, colour ) => {
let region = rootRegion.findChildFromPath(regionPath);
if (region === undefined) {
region = rootRegion.createChildFromPath(regionPath);
}
return region.createLines(groupName, coords, colour);
}
/**
* Create points in region specified in the path
*
*/
this.createPoints = ( regionPath, groupName, coords, labels, colour ) => {
let region = rootRegion.findChildFromPath(regionPath);
if (region === undefined) {
region = rootRegion.createChildFromPath(regionPath);
}
return region.createPoints(groupName, coords, labels, colour);
}
/**
* Add a callback function which will be called everytime zinc object is added.
* @param {Function} callbackFunction - callbackFunction to be added.
*
* @return {Number}
*/
this.addZincObjectAddedCallbacks = callbackFunction => {
zincObjectAddedCallbacks_id = zincObjectAddedCallbacks_id + 1;
zincObjectAddedCallbacks[zincObjectAddedCallbacks_id] = callbackFunction;
return zincObjectAddedCallbacks_id;
}
/**
* Add a callback function which will be called everytime zinc object is removed.
* @param {Function} callbackFunction - callbackFunction to be added.
*
* @return {Number}
*/
this.addZincObjectRemovedCallbacks = callbackFunction => {
zincObjectRemovedCallbacks_id = zincObjectRemovedCallbacks_id + 1;
zincObjectRemovedCallbacks[zincObjectRemovedCallbacks_id] = callbackFunction;
return zincObjectRemovedCallbacks_id;
}
/**
* Remove a callback function that is previously added to the scene.
* @param {Number} id - identifier of the previously added callback function.
*/
this.removeZincObjectAddedCallbacks = id => {
if (id in zincObjectAddedCallbacks_id) {
delete zincObjectAddedCallbacks[id];
}
}
/**
* Remove a callback function that is previously added to the scene.
* @param {Number} id - identifier of the previously added callback function.
*/
this.removeZincObjectRemovedCallbacks = id => {
if (id in zincObjectRemovedCallbacks_id) {
delete zincObjectRemovedCallbacks[id];
}
}
/**
* Clear all zinc object callback function
*/
this.clearZincObjectAddedCallbacks = () => {
zincObjectAddedCallbacks = {};
zincObjectAddedCallbacks_id = 0;
}
/**
* Clear all zinc object callback function
*/
this.clearZincObjectRemovedCallbacks = () => {
zincObjectRemovedCallbacks = {};
zincObjectRemovedCallbacks_id = 0;
}
/**
* Used to trigger zinc object added callback
*/
this.triggerObjectAddedCallback = (zincObject) => {
for (let key in zincObjectAddedCallbacks) {
if (zincObjectAddedCallbacks.hasOwnProperty(key)) {
zincObjectAddedCallbacks[key](zincObject);
}
}
}
/**
* Used to trigger zinc object removed callback
*/
this.triggerObjectRemovedCallback= (zincObject) => {
for (let key in zincObjectRemovedCallbacks) {
if (zincObjectRemovedCallbacks.hasOwnProperty(key)) {
zincObjectRemovedCallbacks[key](zincObject);
}
}
}
/*
* Add temporary points to the scene which can be removed
* with clearTemporaryPrimitives method.
*/
this.addTemporaryPoints = (coords, colour) => {
const geometry = createBufferGeometry(coords.length, coords);
let material = new THREE.PointsMaterial({ alphaTest: 0.5, size: 15,
color: colour, sizeAttenuation: false });
const texture = getCircularTexture();
material.map = texture;
let point = new (require('./three/Points').Points)(geometry, material);
tempGroup.add(point);
return point;
}
/*
* Add temporary lines to the scene which can be removed
* with clearTemporaryPrimitives method.
*/
this.addTemporaryLines = (coords, colour) => {
const geometry = createBufferGeometry(coords.length, coords);
const material = new THREE.LineBasicMaterial({color:colour});
const line = new (require("./three/line/LineSegments").LineSegments)(geometry, material);
tempGroup.add(line);
return line;
}
/*
* Remove object from temporary objects list
*/
this.removeTemporaryPrimitive = (object) => {
tempGroup.remove(object);
object.geometry.dispose();
object.material.dispose();
}
/*
* Remove all temporary primitives.
* Return number of primitives removed;
*/
this.clearTemporaryPrimitives = () => {
let i = 0;
const children = tempGroup.children;
children.forEach(child => {
child.geometry.dispose();
child.material.dispose();
i++;
});
tempGroup.clear();
return i;
}
/*
* Create primitive based on the bounding box of scene and
* add to specify region and group name.
*/
this.addBoundingBoxPrimitive = (regionPath, group, colour, opacity,
visibility, boundingBox = undefined) => {
let region = rootRegion.findChildFromPath(regionPath);
if (region === undefined) {
region = rootRegion.createChildFromPath(regionPath);
}
const box = boundingBox ? boundingBox : this.getBoundingBox();
const dim = new THREE.Vector3().subVectors(box.max, box.min);
const boxGeo = new THREE.BoxGeometry(dim.x, dim.y, dim.z);
const primitive = region.createGeometryFromThreeJSGeometry(
group, boxGeo, colour, opacity, visibility, 10000);
dim.addVectors(box.min, box.max).multiplyScalar( 0.5 );
primitive.setPosition(dim.x, dim.y, dim.z);
return primitive;
}
/*
* Create primitive based on the bounding box of scene and
* add to specify region and group name.
*/
this.addSlicesPrimitive = (regionPath, groups, colours, opacity,
visibility, boundingBox = undefined) => {
if (groups && groups.length >= 3 &&
colours && colours.length >= 3) {
let region = rootRegion.findChildFromPath(regionPath);
if (region === undefined) {
region = rootRegion.createChildFromPath(regionPath);
}
const box = boundingBox ? boundingBox : this.getBoundingBox();
const dim = new THREE.Vector3().subVectors(box.max, box.min);
const directions = ["x", "y", "z"];
const primitives = [];
let index = 0;
directions.forEach((direction) => {
let planeGeo = undefined;
switch(direction) {
//YZ plane
case "x":
planeGeo = new THREE.PlaneGeometry(dim.z, dim.y);
planeGeo.rotateY(Math.PI / 2);
// code block
break;
//XZ plane
case "y":
planeGeo = new THREE.PlaneGeometry(dim.x, dim.z);
planeGeo.rotateX(Math.PI / 2);
// code block
break;
//XY plane
case "z":
planeGeo = new THREE.PlaneGeometry(dim.x, dim.y);
// code block
break;
default:
break;
}
const primitive = region.createGeometryFromThreeJSGeometry(
groups[index], planeGeo, colours[index], opacity, visibility, 10001);
primitives.push(primitive);
index++;
});
dim.addVectors(box.min, box.max).multiplyScalar( 0.5 );
primitives.forEach((primitive) => {
primitive.setPosition(dim.x, dim.y, dim.z);
});
return primitives;
}
}
/*
* Enable marker cluster to work with markers
*/
this.enableMarkerCluster = (flag) => {
if (flag) {
markerCluster.markerUpdateRequired = true;
markerCluster.enable();
} else {
markerCluster.markerUpdateRequired = false;
markerCluster.disable();
}
this.forcePickableObjectsUpdate = true;
}
}