UNPKG

awv3

Version:
875 lines (737 loc) 38 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.MaterialSelector = undefined; var _getIterator2 = require('babel-runtime/core-js/get-iterator'); var _getIterator3 = _interopRequireDefault(_getIterator2); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _set = require('babel-runtime/core-js/set'); var _set2 = _interopRequireDefault(_set); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _three = require('three'); var THREE = _interopRequireWildcard(_three); var _materialstore = require('../../misc/materialstore'); var _materialstore2 = _interopRequireDefault(_materialstore); var _object = require('../../three/object3'); var _object2 = _interopRequireDefault(_object); var _hud = require('../../core/hud'); var _hud2 = _interopRequireDefault(_hud); var _orbit = require('../../controls/orbit'); var _orbit2 = _interopRequireDefault(_orbit); var _combinedcamera = require('../../three/combinedcamera'); var _combinedcamera2 = _interopRequireDefault(_combinedcamera); var _region = require('../../three/region'); var _region2 = _interopRequireDefault(_region); var _elements = require('../store/elements'); var _helpers = require('../helpers'); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var epsilon = 1e-4; /** * @class MaterialSelector is used to select faces, edges, lines or points * (Filter settings: Mesh, LineSegments, Points) * It includes single selection as well as rubberband selection. This selector work * only for materials with meta-data. If the selection-filter includes 'Point', * then point representations are generated for LineSegments and Regions in the * scene and scaled to achieve the desired radius (pointRadiusPx). */ var MaterialSelector = exports.MaterialSelector = function () { function MaterialSelector(session) { var _this = this; (0, _classCallCheck3.default)(this, MaterialSelector); this.session = session; // Wait until the sessions pool has been added into the view (happens in <App/>) this.session.pool.viewFound().then(function (view) { return _this.view = view; }); //the currently active selecion-element this.element = undefined; //stores some of the original material properties this.materialStore = new _materialstore2.default(); // stores the material(s) of the selected object(s) this.selectedSet = new _set2.default(); //the unobserve function is assigned when selector is activated //and must be called when deactivated this.unobserve = undefined; // initial size of point geometry this.pointSize = 1; // desired radius of point sphere in px this.pointRadiusPx = 5; // desired tolerance for picking lines this.linePrecisionPx = 5; // property changes for hovered materials this.hoveredProps = { color: new THREE.Color(0x28d79f), opacity: 1, linewidth: 3 }; // property changes for selected materials this.selectedProps = { color: new THREE.Color(0xc23369), opacity: 1, linewidth: 3 }; } /** * Deactivates the material-selector. Observation of selection-element is stopped. * Rubberband selection handlers are removed. */ (0, _createClass3.default)(MaterialSelector, [{ key: 'deactivate', value: function deactivate() { this.removeAll(false); this.shiftHandler && document.removeEventListener('keydown', this.shiftHandler); this.shiftHandler = undefined; this.element = undefined; this.unobserve && this.unobserve(); this.points && this.points.destroy(); } /** * Activates the material-selector. The Given selection-element is observed in * order to register changes when click-deleting labels. Handlers for * rubberband selection are created. */ }, { key: 'activate', value: function activate() { var _this2 = this; var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.element; //observe changes in element's children this.unobserve = this.session.observe(function (state) { return (state.elements[element.id] || {}).items; }, function () { for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _helpers.arrayDiff.apply(undefined, args.concat([function (newItems) {}, function (deletedItems) { var candidates = []; var _loop = function _loop(i) { _this2.selectedSet.forEach(function (material) { if (deletedItems[i] === material.meta.id) { candidates.push(material); } }); }; for (var i = 0; i < deletedItems.length; i++) { _loop(i); } candidates.forEach(function (material) { return _this2.remove({ material: material }); }); }])); }); this.element = element; // Create new group for points this.points = new _object2.default(); this.session.pool.add(this.points); // check for multiple points with same position this.positions = []; // Create points, center points, etc if (element.types.includes('Point')) { this.session.pool.traverse(function (item) { if (item.type === 'Region') { item.points.forEach(function (obj) { if (_this2.session.isVisibleLayer(obj.meta.layer)) { _this2.addPoint(obj.meta.position, (0, _extends3.default)({}, obj.meta, { id: obj.id })); } }); } else if (item.type === 'LineSegments' && item.interactive) { item.material.forEach(function (line) { if (!_this2.session.isVisibleLayer(line.meta.layer)) return; switch (line.meta.type) { case 'circle': case 'arc': // center point will be added when a circle/arc is selected break; case 'line': var start = item.localToWorld(line.meta.start); var end = item.localToWorld(line.meta.end); _this2.addPoint(start, (0, _extends3.default)({}, line.meta, { id: String(line.meta.id) + '_s', position: line.meta.start, type: 'point' })); _this2.addPoint(end, (0, _extends3.default)({}, line.meta, { id: String(line.meta.id) + '_e', position: line.meta.end, type: 'point' })); break; } }); } }); } this.points.children && this.points.children.forEach(function (point) { return point.setRenderOrder({ Mesh: 0, LineSegments: 100 }); }); // restore previously selected elements if (this.element.items.length > 0) { this.session.pool.traverse(function (obj) { if (obj.material) { var isMultiMaterial = Array.isArray(obj.material); if (isMultiMaterial) { obj.material.forEach(function (mat) { if (mat.meta && _this2.element.items.indexOf(mat.meta.id) > -1) { _this2.add({ material: mat }, false); } }); } else if (obj.material.meta && _this2.element.items.indexOf(obj.material.meta.id) > -1) { _this2.add({ material: obj.material }, false); } } }); } var types = ['Scene']; if (Array.isArray(element.types)) types = [].concat((0, _toConsumableArray3.default)(types), (0, _toConsumableArray3.default)(element.types));else if (typeof element.types === 'function') types = function types(object) { return object.type === 'Scene' || element.types(object); }; // SHIFT KEY selection handler var candidates = []; this.shiftHandler = function (event) { if (!_this2.shiftHandler.handled && event.keyCode === 16) { _this2.shiftHandler.handled = true; _this2.view.input.debounce = false; _this2.view.controls.enabled = false; _this2.view.interaction.enabled = false; var boundingBoxes2D = []; _this2.view.scene.traverseMaterials(function (material, item) { // let value = Object3.RenderOrder.MeshesFirst[item.type]; // if (value !== undefined) item.renderOrder = value; //project bounding box to view coordinates if (item.visible && item.interactive && material.meta && types.includes(item.type)) { item.updateMatrixWorld(true); var minPt = material.meta.box.min.clone(); //TODO: get all 8 points var maxPt = material.meta.box.max.clone(); //TODO: get all 8 points //project to view-coordinates var p1 = _this2.view.getPoint2(minPt); var p2 = _this2.view.getPoint2(maxPt); //bounding box in 2D var bounds2D = new THREE.Box2(); bounds2D.setFromPoints([p1, p2]); bounds2D.__material = material; bounds2D.__object = item; boundingBoxes2D.push(bounds2D); } }); // Init new heads up display, add it to the view _this2.hud = new _hud2.default(_this2.view); _this2.view.addHud(_this2.hud); _this2.hud.controls = undefined; _this2.hud.camera = new _combinedcamera2.default({ near: _this2.view.camera.near, far: _this2.view.camera.far, fov: _this2.view.camera.fov }); _this2.view.measure(true); var modelBounds = _this2.view.scene.updateBounds().bounds; var geom = new THREE.PlaneGeometry(1, 1); var box = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({ transparent: true, opacity: 0.05, color: new THREE.Color(0) })); _this2.hud.scene.add(box); var clickDown = void 0, mousePos = void 0, rubberBandLine = void 0, clickHud = void 0; var handler = function handler(event) { switch (event.type) { case 'mousedown': clickDown = new THREE.Vector2(event.offsetX, event.offsetY); mousePos = new THREE.Vector2(event.offsetX + 11, event.offsetY + 1); clickHud = _this2.view.getPoint3({ x: event.offsetX, y: event.offsetY }, _this2.hud.camera.display, 0); box.position.copy(clickHud); break; case 'mousemove': if (!clickDown) return; //update rectangle var tempPoint = _this2.view.getPoint3({ x: event.offsetX, y: event.offsetY }, _this2.hud.camera.display, 0); var delta = tempPoint.sub(clickHud); var width = Math.abs(delta.x); var height = Math.abs(delta.y); var halfHeight = height / 2; var halfWidth = width / 2; var halfDeltaX = delta.x / 2; var halfDeltaY = delta.y / 2; geom.vertices[0].set(-halfWidth + halfDeltaX, halfHeight + halfDeltaY, 0); geom.vertices[1].set(width - halfWidth + halfDeltaX, halfHeight + halfDeltaY, 0); geom.vertices[2].set(-halfWidth + halfDeltaX, -(height - halfHeight) + halfDeltaY, 0); geom.vertices[3].set(width - halfWidth + halfDeltaX, -(height - halfHeight) + halfDeltaY, 0); geom.verticesNeedUpdate = true; mousePos = new THREE.Vector2(event.offsetX, event.offsetY); //check intersection with rectangle in display-coordinates var rubberBandRectangle = new THREE.Box2(); rubberBandRectangle.setFromPoints([clickDown, mousePos]); var newCandidates = []; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = (0, _getIterator3.default)(boundingBoxes2D), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var _box = _step.value; if (rubberBandRectangle.containsBox(_box)) { // create new object identical to raycast-hit newCandidates.push({ distance: 0, point: undefined, face: undefined, faceIndex: undefined, indices: undefined, object: _box.__object, material: _box.__material, meta: _box.__material.meta }); } } // Remove items not present any longer } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = (0, _getIterator3.default)(candidates), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var item = _step2.value; var found = false; var _iteratorNormalCompletion4 = true; var _didIteratorError4 = false; var _iteratorError4 = undefined; try { for (var _iterator4 = (0, _getIterator3.default)(newCandidates), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { var newItem = _step4.value; if (item.material.meta.id === newItem.material.meta.id) { found = true; break; } } } catch (err) { _didIteratorError4 = true; _iteratorError4 = err; } finally { try { if (!_iteratorNormalCompletion4 && _iterator4.return) { _iterator4.return(); } } finally { if (_didIteratorError4) { throw _iteratorError4; } } } if (!found) { _this2.remove(item, false); } } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } candidates = newCandidates; // Add new items, but only if they aren't known yet var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = (0, _getIterator3.default)(candidates), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var _item = _step3.value; _this2.add(_item, false); } } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3.return) { _iterator3.return(); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } _this2.view.invalidate(); break; case 'mouseup': box && box.destroy(); var tempIds = []; candidates.forEach(function (item) { return tempIds.push(item.material.meta.id); }); clickDown = undefined; if (rubberBandLine != undefined) { _this2.view.scene.remove(rubberBandLine); rubberBandLine = undefined; } // Update UI after mouse-up _this2.session.store.dispatch(_elements.actions.update(_this2.element.id, { items: candidates.map(function (item) { return item.material.meta.id; }) })); break; } }; _this2.view.input.on(['mousedown', 'mousemove', 'mouseup'], handler); _this2.tempHandler = function () { // Remove and destroy heads up display _this2.view.removeHud(_this2.hud); _this2.hud.destroy(); _this2.shiftHandler.handled = false; _this2.view.input.removeListener(['mousedown', 'mousemove', 'mouseup'], handler); document.removeEventListener('keyup', _this2.tempHandler); _this2.view.input.debounce = true; _this2.view.controls.enabled = true; _this2.view.interaction.enabled = true; }; document.addEventListener('keyup', _this2.tempHandler); } }; this.shiftHandler.handled = false; document.addEventListener('keydown', this.shiftHandler); } /** * Add material to the set of selected elements * @param {RayCast-Hit} event - Object generated by the raycaster including the * hit object, material and meta-information */ }, { key: 'add', value: function add(event) { var notify = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; // If the elements limit is met, all prior selections should be removed if (this.selectedSet.size >= this.element.limit) { this.removeAll(); } if (this.isSelected(event.material)) return event; switch (event.material.meta.type) { // add a center point when a circle/arc is selected case 'circle': case 'arc': this.addPoint(event.material.meta.center, (0, _extends3.default)({}, event.material.meta, { id: String(event.material.meta.id) + '_c', position: event.material.meta.center, type: 'point' }), 0.1); break; // deselect all circles/arcs when a center point is selected case 'point': var _iteratorNormalCompletion5 = true; var _didIteratorError5 = false; var _iteratorError5 = undefined; try { for (var _iterator5 = (0, _getIterator3.default)(this.selectedSet), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { var material = _step5.value; switch (material.meta.type) { case 'circle': case 'arc': if (material.meta.center.distanceToSquared(event.material.meta.position) < epsilon * epsilon) this.remove({ material: material }, notify); break; } } } catch (err) { _didIteratorError5 = true; _iteratorError5 = err; } finally { try { if (!_iteratorNormalCompletion5 && _iterator5.return) { _iterator5.return(); } } finally { if (_didIteratorError5) { throw _iteratorError5; } } } break; } this.select(event.material); this.selectedSet.add(event.material); notify && this.session.store.dispatch(_elements.actions.update(this.element.id, { items: this.getSelectedIds() })); return event; } /** * Remove material from the set of selected elements * @param {RayCast-Hit} event - Object generated by the raycaster including the * hit object, material and meta-information */ }, { key: 'remove', value: function remove(event) { var notify = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; if (!this.isSelected(event.material)) return; this.unselect(event.material); this.selectedSet.delete(event.material); notify && this.session.store.dispatch(_elements.actions.removeChild(this.element.id, event.material.meta.id, 'items')); } /** * Remove all items (Material) from the set of selected elements * @param {Boolean} keepLocals - If true, selected ids are kept within the selection-element */ }, { key: 'removeAll', value: function removeAll() { var _this3 = this; var removeElements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; this.selectedSet.forEach(function (material) { return _this3.unselect(material); }); this.selectedSet.clear(); if (removeElements) { this.session.store.dispatch(_elements.actions.removeAllChilds(this.element.id, 'items')); } } /** * Called by selector when event is triggered. */ }, { key: 'hover', value: function hover(event) { if (this.shiftHandler.handled) return; this.session.pool.view.setCursor('pointer'); if (event.material && !this.isSelected(event.material)) this.materialStore.store(event.material, this.hoveredProps).start(500); } /** * Called by selector when event is triggered. */ }, { key: 'unhover', value: function unhover(event) { if (this.shiftHandler.handled) return; if (event.material) { if (this.isSelected(event.material)) { this.materialStore.storedPropertiesMap.get(event.material).refcount--; } else { this.materialStore.restore(event.material).start(500); } } } /** * Called by selector when event is triggered. */ }, { key: 'clicked', value: function clicked(event) { if (this.shiftHandler.handled) return; if (!event.material.meta) return; if (this.isSelected(event.material)) { this.remove(event); } else { this.add(event); } } /** * Called by selector when event is triggered. */ }, { key: 'missed', value: function missed(event) { this.removeAll(true); } /** * Called by selector when event is triggered. */ }, { key: 'rendered', value: function rendered(event) { this.scalePoints(); this.calculateLinePrecision(); } /** * Highlight the selected material. Store original material for unhighlight. * @param {THREE.Material} - Material to select. */ }, { key: 'select', value: function select(material) { this.materialStore.store(material, this.selectedProps).start(500); } /** * Unhighlights the selected material, restoring the original appearance. * @param {THREE.Material} - Material to unselect. */ }, { key: 'unselect', value: function unselect(material) { if (this.view.interaction.hitsArray.find(function (hit) { return hit.material === material; })) { this.materialStore.store(material, this.hoveredProps).start(500); } else { this.materialStore.restore(material).start(500); } } /** * Check if material object is already selected * @return {bool} TRUE if selected, FALSE otherwise */ }, { key: 'isSelected', value: function isSelected(material) { return this.selectedSet.has(material); } /** * Returns the selected ids in an array * @return {array} selectedIds - Array with the selected ids */ }, { key: 'getSelectedIds', value: function getSelectedIds() { var iter = this.selectedSet.values(); var selectedIds = []; var item = iter.next(); while (item.value !== undefined) { selectedIds.push(item.value.meta.id); item = iter.next(); } return selectedIds; } /** * Returns the selected elements in an array * @return {array} selectedElements - Array with the selected elements */ }, { key: 'getSelectedElements', value: function getSelectedElements() { var iter = this.selectedSet.values(); var selectedElements = []; var item = iter.next(); while (item.value !== undefined) { selectedElements.push(item.value); item = iter.next(); } return selectedElements; } /** * Scales all the created points to the desired radius (pointRadiusPx) */ }, { key: 'scalePoints', value: function scalePoints() { var _this4 = this; if (this.points && this.points.children.length > 0) { this.points.children.forEach(function (point) { var scaleFactor = _this4.session.pool.view.calculateScaleFactor(point.position, _this4.pointRadiusPx); point.scale.set(scaleFactor, scaleFactor, scaleFactor); }); } } /** * Add a point to the points object in the scene. Duplicate points are * filtered out. * @param {THREE.Vector3} position - Position of the point to be added. * @param {object} meta - Meta-data of the object/material this point belongs to. */ }, { key: 'addPoint', value: function addPoint(position, meta) { var opacity = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; if (this.isDuplicatePoint(position)) { return; } var geometry = new THREE.SphereBufferGeometry(this.pointSize, 32, 32); geometry.computeBoundingSphere(); geometry.computeBoundingBox(); // material var material = new THREE.MeshBasicMaterial({ color: 0x28b4d7, transparent: true, opacity: opacity }); // point as sphere-mesh var point = new THREE.Mesh(geometry, material); point.updateParentMaterials = false; point.renderOrder = 10000; point.type = 'Point'; point.position.copy(position); var vec = new THREE.Vector3(this.pointSize, this.pointSize, this.pointSize); var box = new THREE.Box3(position.sub(vec), position.add(vec)); point.userData.meta = material.meta = meta; var scaleFactor = this.session.pool.view.calculateScaleFactor(point.position, this.pointRadiusPx); point.scale.set(scaleFactor, scaleFactor, scaleFactor); this.points.add(point); this.positions.push(position); } /** * Checks for existing points within proximity of given position * @return true if a point exists already for the given position (within tolerance epsilon) */ }, { key: 'isDuplicatePoint', value: function isDuplicatePoint(position) { var _iteratorNormalCompletion6 = true; var _didIteratorError6 = false; var _iteratorError6 = undefined; try { for (var _iterator6 = (0, _getIterator3.default)(this.positions), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { var hasposition = _step6.value; if (hasposition.distanceToSquared(position) < epsilon * epsilon) return true; } } catch (err) { _didIteratorError6 = true; _iteratorError6 = err; } finally { try { if (!_iteratorNormalCompletion6 && _iterator6.return) { _iterator6.return(); } } finally { if (_didIteratorError6) { throw _iteratorError6; } } } return false; } /** * Calculate and set the linePrecision the given position based on the given * precision in pixel. */ }, { key: 'calculateLinePrecision', value: function calculateLinePrecision() { if (this.session.pool.view && this.session.pool.scene.bounds.sphere) { var center = this.session.pool.scene.bounds.sphere.center; var point2 = this.session.pool.view.getPoint2(center.clone()); var screenPoint1 = new THREE.Vector3(0, 0, point2.z); var screenPoint2 = new THREE.Vector3(0, 0, point2.z); screenPoint2.x = this.linePrecisionPx; var worldPoint1 = this.session.pool.view.getPoint3(screenPoint1, this.session.pool.view.camera, screenPoint1.z); var worldPoint2 = this.session.pool.view.getPoint3(screenPoint2, this.session.pool.view.camera, screenPoint2.z); this.session.pool.view.interaction.raycaster.linePrecision = worldPoint1.sub(worldPoint2).length(); } } /** * Removes the given elements from the current selection. * @param {array} elements - elements to be removed from the selection. */ }, { key: 'unselectElements', value: function unselectElements(elements) { var _this5 = this; elements.forEach(function (element) { return _this5.remove({ material: element }); }); } }]); return MaterialSelector; }();