UNPKG

awv3

Version:
819 lines (683 loc) 37.7 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.MaterialSelector = undefined; var _typeof2 = require('babel-runtime/helpers/typeof'); var _typeof3 = _interopRequireDefault(_typeof2); 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 _orbit = require('../../controls/orbit'); var _orbit2 = _interopRequireDefault(_orbit); var _perspective = require('../../three/perspective'); var _perspective2 = _interopRequireDefault(_perspective); 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 }; } /** * @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] || {}).children; }, 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, midpoints, 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.materials.forEach(function (line) { switch (line.meta.type) { case 'arc': if (_this2.session.isVisibleLayer(line.meta.layer)) { _this2.addPoint(line.meta.center, (0, _extends3.default)({}, line.meta, { id: line.meta.id + '_c', position: line.meta.center, type: 'point' }), 0.1); } break; case 'line': if (_this2.session.isVisibleLayer(line.meta.layer)) { _this2.addPoint(line.meta.start, (0, _extends3.default)({}, line.meta, { id: line.meta.id + '_s', position: line.meta.start, type: 'point' })); _this2.addPoint(line.meta.end, (0, _extends3.default)({}, line.meta, { id: line.meta.id + '_e', position: line.meta.end, type: 'point' })); } break; case 'circle': if (_this2.session.isVisibleLayer(line.meta.layer)) { _this2.addPoint(line.meta.center, (0, _extends3.default)({}, line.meta, { id: line.meta.id + '_c', position: line.meta.center, 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.children.length > 0) { this.session.pool.traverse(function (obj) { if (obj.material) { if (obj.material instanceof THREE.MultiMaterial) { obj.material.materials.forEach(function (mat) { if (mat.meta && _this2.element.children.indexOf(mat.meta.id) > -1) { _this2.session.store.dispatch(_elements.actions.removeChild(_this2.element.id, mat.meta.id)); _this2.add({ material: mat }); } }); } else if (obj.material.meta && _this2.element.children.indexOf(obj.material.meta.id) > -1) { _this2.session.store.dispatch(_elements.actions.removeChild(_this2.element.id, obj.material.meta.id)); _this2.add({ material: obj.material }); } } }); } 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) { (function () { _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); } }); //draw rectangle var nearDistance = _this2.view.camera.near; var farDistance = _this2.view.camera.far; _this2.view.hud = true; _this2.view.controlsHud = undefined; _this2.view.cameraHud = new _perspective2.default({ near: nearDistance, far: farDistance, 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) })); box.renderOrder = -1; _this2.view.sceneHud.add(box); var clickDown = void 0, mousePos = void 0, rubberBandLine = void 0, clickHud = void 0; var handler = function handler(event) { var _ret3 = function () { 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.view.cameraHud, 0); box.position.copy(clickHud); break; case 'mousemove': if (!clickDown) return { v: void 0 }; //update rectangle var tempPoint = _this2.view.getPoint3({ x: event.offsetX, y: event.offsetY }, _this2.view.cameraHud, 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, { children: candidates.map(function (item) { return item.material.meta.id; }) })); break; } }(); if ((typeof _ret3 === 'undefined' ? 'undefined' : (0, _typeof3.default)(_ret3)) === "object") return _ret3.v; }; _this2.view.input.on(['mousedown', 'mousemove', 'mouseup'], handler); _this2.tempHandler = function () { box && box.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; _this2.view.scene.setRenderOrder(); }; 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 (!this.isSelected(event.material)) { this.select(event.material); this.selectedSet.add(event.material); notify && this.session.store.dispatch(_elements.actions.addChild(this.element.id, event.material.meta.id)); } 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)) { this.unselect(event.material); this.selectedSet.delete(event.material); notify && this.session.store.dispatch(_elements.actions.removeChild(this.element.id, event.material.meta.id)); } } /** * 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)); } } /** * 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 && !this.isSelected(event.material)) 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 (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.length) { 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.calculatePointScaleFactor(point.position); point.scale.set(scaleFactor, scaleFactor, scaleFactor); }); } } /** * Calculate scale factor for the given position based on the desired radius. * @return {real} scaleFactor - Calculated scale factor for the given position. */ }, { key: 'calculatePointScaleFactor', value: function calculatePointScaleFactor(position) { var scaleFactor = 1.0; if (this.session.pool.view) { var point2 = this.session.pool.view.getPoint2(position.clone()); var screenPoint1 = new THREE.Vector3(0, 0, point2.z); var screenPoint2 = new THREE.Vector3(0, 0, point2.z); screenPoint2.x = this.pointRadiusPx; 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); var distance = worldPoint1.sub(worldPoint2).length(); scaleFactor = distance / this.pointSize; } return 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.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.calculatePointScaleFactor(position); 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 epsilon = 1e-4; for (var i = 0; i < this.positions.length; i++) { if (Math.abs(this.positions[i].x - position.x) < epsilon && Math.abs(this.positions[i].y - position.y) < epsilon && Math.abs(this.positions[i].z - position.z) < epsilon) { return true; } } 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; }();