UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

347 lines (297 loc) 10.5 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var THREE = _interopRequireWildcard(require("three")); var _RenderMode = _interopRequireDefault(require("../Renderer/RenderMode")); var _LayeredMaterial = require("../Renderer/LayeredMaterial"); function hideEverythingElse(view) { var threejsLayer = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; // We want to render only 'object' and its hierarchy. // So if it uses threejsLayer defined -> force it on the camera // (or use the default one: 0) var prev = view.camera.camera3D.layers.mask; view.camera.camera3D.layers.mask = 1 << threejsLayer; return function () { view.camera.camera3D.layers.mask = prev; }; } var depthRGBA = new THREE.Vector4(); // TileMesh picking support function function screenCoordsToNodeId(view, tileLayer, viewCoords) { var radius = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0; var dim = view.mainLoop.gfxEngine.getWindowSize(); viewCoords = viewCoords || new THREE.Vector2(Math.floor(dim.x / 2), Math.floor(dim.y / 2)); var restore = tileLayer.level0Nodes.map(function (n) { return _RenderMode["default"].push(n, _RenderMode["default"].MODES.ID); }); var undoHide = hideEverythingElse(view, tileLayer.object3d, tileLayer.threejsLayer); var buffer = view.mainLoop.gfxEngine.renderViewToBuffer({ camera: view.camera, scene: tileLayer.object3d }, { x: viewCoords.x - radius, y: viewCoords.y - radius, width: 1 + radius * 2, height: 1 + radius * 2 }); undoHide(); restore.forEach(function (r) { return r(); }); var ids = []; traversePickingCircle(radius, function (x, y) { var idx = (y * 2 * radius + x) * 4; var data = buffer.slice(idx, idx + 4 || undefined); depthRGBA.fromArray(data).divideScalar(255.0); var unpack = (0, _LayeredMaterial.unpack1K)(depthRGBA, Math.pow(256, 3)); var _id = Math.round(unpack); if (!ids.includes(_id)) { ids.push(_id); } }); return ids; } function traversePickingCircle(radius, callback) { // iterate on radius so we get closer to the mouse // results first. // Result traversal order for radius=2 // --3-- // -323- // 32123 // -323 // --3-- var prevSq; for (var r = 0; r <= radius; r++) { var sq = r * r; for (var x = -r; x <= r; x++) { var sqx = x * x; for (var y = -r; y <= r; y++) { var dist = sqx + y * y; // skip if too far if (dist > sq) { continue; } // skip if belongs to previous if (dist <= prevSq) { continue; } if (callback(x, y) === false) { return; } } } prevSq = sq; } } function findLayerInParent(obj) { if (obj.layer) { return obj.layer; } if (obj.parent) { return findLayerInParent(obj.parent); } } var raycaster = new THREE.Raycaster(); /** * @module Picking * * Implement various picking methods for geometry layers. * These methods are not meant to be used directly, see View.pickObjectsAt * instead. * * All the methods here takes the same parameters: * - the View instance * - view coordinates (in pixels) where picking should be done * - radius (in pixels) of the picking circle * - layer: the geometry layer used for picking */ var _default = { pickTilesAt: function pickTilesAt(view, viewCoords, radius, layer) { var results = []; var _ids = screenCoordsToNodeId(view, layer, viewCoords, radius); var extractResult = function (node) { if (_ids.includes(node.id) && node.isTileMesh) { results.push({ object: node, layer: layer }); } }; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = layer.level0Nodes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var n = _step.value; n.traverse(extractResult); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator["return"] != null) { _iterator["return"](); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return results; }, pickPointsAt: function pickPointsAt(view, viewCoords, radius, layer) { if (!layer.root) { return; } // enable picking mode for points material layer.object3d.traverse(function (o) { if (o.isPoints && o.baseId) { o.material.enablePicking(true); } }); var undoHide = hideEverythingElse(view, layer.object3d, layer.threejsLayer); // render 1 pixel // TODO: support more than 1 pixel selection var buffer = view.mainLoop.gfxEngine.renderViewToBuffer({ camera: view.camera, scene: layer.object3d }, { x: viewCoords.x - radius, y: viewCoords.y - radius, width: 1 + radius * 2, height: 1 + radius * 2 }); undoHide(); var candidates = []; traversePickingCircle(radius, function (x, y) { var idx = (y * 2 * radius + x) * 4; var data = buffer.slice(idx, idx + 4); // see PointCloudProvider and the construction of unique_id var objId = data[0] << 8 | data[1]; var index = data[2] << 8 | data[3]; var r = { objId: objId, index: index }; for (var i = 0; i < candidates.length; i++) { if (candidates[i].objId == r.objId && candidates[i].index == r.index) { return; } } candidates.push(r); }); var result = []; layer.object3d.traverse(function (o) { if (o.isPoints && o.baseId) { // disable picking mode o.material.enablePicking(false); // if baseId matches objId, the clicked point belongs to `o` for (var i = 0; i < candidates.length; i++) { if (candidates[i].objId == o.baseId) { result.push({ object: o, index: candidates[i].index, layer: layer }); } } } }); return result; }, /* * Default picking method. Uses THREE.Raycaster */ pickObjectsAt: function pickObjectsAt(view, viewCoords, radius, object) { var target = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : []; if (radius < 0) { var _normalized = view.viewToNormalizedCoords(viewCoords); raycaster.setFromCamera(_normalized, view.camera.camera3D); var intersects = raycaster.intersectObject(object, true); var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = intersects[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var inter = _step2.value; inter.layer = findLayerInParent(inter.object); target.push(inter); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) { _iterator2["return"](); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } return target; } // Instead of doing N raycast (1 per x,y returned by traversePickingCircle), // we force render the zone of interest. // Then we'll only do raycasting for the pixels where something was drawn. var zone = { x: viewCoords.x - radius, y: viewCoords.y - radius, width: 1 + radius * 2, height: 1 + radius * 2 }; var pixels = view.mainLoop.gfxEngine.renderViewToBuffer({ scene: object, camera: view.camera }, zone); var clearColor = view.mainLoop.gfxEngine.renderer.getClearColor(); var clearR = Math.round(255 * clearColor.r); var clearG = Math.round(255 * clearColor.g); var clearB = Math.round(255 * clearColor.b); // Raycaster use NDC coordinate var normalized = view.viewToNormalizedCoords(viewCoords); var tmp = normalized.clone(); traversePickingCircle(radius, function (x, y) { // x, y are offset from the center of the picking circle, // and pixels is a square where 0, 0 is the top-left corner. // So we need to shift x,y by radius. var offset = ((y + radius) * (radius * 2 + 1) + (x + radius)) * 4; var r = pixels[offset]; var g = pixels[offset + 1]; var b = pixels[offset + 2]; // Use approx. test to avoid rounding error or to behave // differently depending on hardware rounding mode. if (Math.abs(clearR - r) <= 1 && Math.abs(clearG - g) <= 1 && Math.abs(clearB - b) <= 1) { // skip because nothing has been rendered here return; } // Perform raycasting tmp.setX(normalized.x + x / view.camera.width).setY(normalized.y + y / view.camera.height); raycaster.setFromCamera(tmp, view.camera.camera3D); var intersects = raycaster.intersectObject(object, true); var _iteratorNormalCompletion3 = true; var _didIteratorError3 = false; var _iteratorError3 = undefined; try { for (var _iterator3 = intersects[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { var _inter = _step3.value; _inter.layer = findLayerInParent(_inter.object); target.push(_inter); } // Stop at first hit } catch (err) { _didIteratorError3 = true; _iteratorError3 = err; } finally { try { if (!_iteratorNormalCompletion3 && _iterator3["return"] != null) { _iterator3["return"](); } } finally { if (_didIteratorError3) { throw _iteratorError3; } } } return target.length == 0; }); return target; } }; exports["default"] = _default;