itowns
Version:
A JS/WebGL framework for 3D geospatial data visualization
347 lines (297 loc) • 10.5 kB
JavaScript
"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;