const THREE = require('three'); const markerImage = new Image(128, 128); markerImage.src = require("../assets/mapMarkerOrange.svg"); const texture = new THREE.Texture(); texture.image = markerImage; texture.needsUpdate = true; const size = [0.02, 0.03, 1]; const spriteMaterial = new THREE.SpriteMaterial({ map: texture, alphaTest: 0.5, transparent: true, depthTest: false, depthWrite: false, sizeAttenuation: false }); const createNewSpriteText = require('../utilities').createNewSpriteText; /** * A special graphics type with a tear drop shape. * It forms a cluster a marker based on distance. * * @class * @author Alan Wu * @return {Marker} */ const MarkerCluster = function(sceneIn) { (require('./zincObject').ZincObject).call(this); this.texture = texture; let sprite = undefined; let scene = sceneIn; this.morph = new THREE.Group(); this.group = this.morph; this.isMarkerCluster = true; let enabled = true; let sprites = []; this.markers = {}; let _v21 = new THREE.Vector2(); let _v22 = new THREE.Vector2(); let _radius = 0.1; let start = Date.now(); /** * Set the size of the marker. * * @param {Number} size - size to be set. */ this.setSpriteSize = size => { sprite.scale.set(0.015, 0.02, 1); sprite.scale.multiplyScalar(size); } this.clear = () => { this.group.clear(); this.markers = {}; } /** * Clean up this object, */ this.dispose = () => { this.clear(); if (this.morph) { this.morph.clear(); } } const createNewSprite = (index) => { //Group is needed to set the position after scaling //the sprite const localGroup = new THREE.Group(); const sprite = new THREE.Sprite(spriteMaterial); sprite.clusterIndex = index; sprite.center.set(0.5, 0); sprite.position.set(0, 0, 0); sprite.renderOrder = 10000; sprite.scale.set(size[0], size[1], size[2]); sprite.userData = this; localGroup.add(sprite); this.group.add(localGroup); return { "group": localGroup, "marker": sprite, "label": undefined, "number": 0, "min": [0, 0, 0], "max": [1, 1, 1], }; } const activateSpriteForCluster = (sprite, cluster, number) => { sprite.group.visible = true; sprite.group.position.set( cluster.coords[0], cluster.coords[1], cluster.coords[2] ); if (sprite.label === undefined || (number !== sprite.number)) { if (sprite.label) { sprite.group.remove(sprite.label); sprite.label.material.map.dispose(); sprite.label.material.dispose(); } sprite.label = createNewSpriteText(number, 0.012, "black", "Asap", 50, 500); sprite.number = number; sprite.group.add(sprite.label); } sprite.min = cluster.min; sprite.max = cluster.max; } const drawClusters = (clusters) => { let currentIndex = 0; clusters.forEach((cluster) => { const length = cluster.members.length; let number = 0; if (length === 1) { cluster.members[0].setVisibility(true); } else { cluster.members.forEach((marker) => { number += marker.getNumber(); marker.setVisibility(false); }); if (!sprites[currentIndex]) { sprites.push(createNewSprite(currentIndex)); } activateSpriteForCluster(sprites[currentIndex], cluster, number); currentIndex++; } }); for (currentIndex; currentIndex < sprites.length; currentIndex++) { sprites[currentIndex].group.visible = false; } } //Get clusters based on the ndc coordinate for each cluster. const getCluster = (markersObj, clusters) => { let first = true; let newCluster = {members: [], coords: [0,0,0], min: [0, 0, 0], max: [1, 1, 1]}; let dist = 0 for (let prop in markersObj) { if (first) { _v21.set(markersObj[prop].ndc.x, markersObj[prop].ndc.y); this._b1.setFromPoints([markersObj[prop].morph.position]); first = false; newCluster.members.push(markersObj[prop]); newCluster.coords = [ markersObj[prop].morph.position.x, markersObj[prop].morph.position.y, markersObj[prop].morph.position.z, ] clusters.push(newCluster); delete markersObj[prop]; } else { _v22.set(markersObj[prop].ndc.x, markersObj[prop].ndc.y); dist = _v21.distanceTo(_v22); if (_radius > dist) { newCluster.members.push(markersObj[prop]); this._b1.expandByPoint(markersObj[prop].morph.position); delete markersObj[prop]; } } } newCluster.min = [this._b1.min.x, this._b1.min.y, this._b1.min.z]; newCluster.max = [this._b1.max.x, this._b1.max.y, this._b1.max.z]; //this._b1.getCenter(this._v2); //newCluster.coords = [this._v2.x, this._v2.y, this._v2.z]; //The following will not be called if there is object left and //thus finishing clustering if (first !== true) { getCluster(markersObj, clusters); } } this.calculate = () => { if (enabled) { const current = Date.now(); if ((current - start) > 500) { let clusters = []; getCluster({...this.markers}, clusters); drawClusters(clusters); start = Date.now(); this.markerUpdateRequired = false; } } } this.isEnabled = () => { return enabled; } /** * Enable and visualise the marker. */ this.enable = () => { enabled = true; this.morph.visible = true; } /** * Disable and hide the marker. */ this.disable = () => { enabled = false; this.morph.visible = false; //turn all markers back on for (let prop in this.markers) { if (this.markers[prop]?.isMarker && this.markers[prop].isEnabled()) { this.markers[prop].setVisibility(true); } } } this.zoomToCluster = (index) => { if (index !== undefined && index > -1) { this._v1.set(...sprites[index].min); this._v2.set(...sprites[index].max); if (Math.abs(this._v1.distanceTo(this._v2) > 0.0)) { this._b1.set(this._v1, this._v2); scene.translateBoundingBoxToCameraView(this._b1, 3, 300); this.markerUpdateRequired = true; return true; } } return false; } this.clusterIsVisible = (index) => { if (index !== undefined && index > -1) { if (sprites[index]) { return sprites[index].group?.visible; } } return false; } } MarkerCluster.prototype = Object.create((require('./zincObject').ZincObject).prototype); exports.MarkerCluster = MarkerCluster;