@sauskylark/potree
Version:
WebGL point cloud viewer
1,140 lines (853 loc) • 30.5 kB
JavaScript
import * as THREE from "../../libs/three.js/build/three.module.js";
import {Utils} from "../utils.js";
import {Points} from "../Points.js";
import {DXFProfileExporter} from "../exporter/DXFProfileExporter.js";
import {CSVExporter} from "../exporter/CSVExporter.js";
import {LASExporter} from "../exporter/LASExporter.js";
import { EventDispatcher } from "../EventDispatcher.js";
import {PointCloudTree} from "../PointCloudTree.js";
import {Renderer} from "../PotreeRenderer.js";
import {PointCloudMaterial} from "../materials/PointCloudMaterial.js";
import {PointSizeType} from "../defines.js";
function copyMaterial(source, target){
for(let name of Object.keys(target.uniforms)){
target.uniforms[name].value = source.uniforms[name].value;
}
target.gradientTexture = source.gradientTexture;
target.visibleNodesTexture = source.visibleNodesTexture;
target.classificationTexture = source.classificationTexture;
target.matcapTexture = source.matcapTexture;
target.activeAttributeName = source.activeAttributeName;
target.ranges = source.ranges;
//target.updateShaderSource();
}
class Batch{
constructor(geometry, material){
this.geometry = geometry;
this.material = material;
this.sceneNode = new THREE.Points(geometry, material);
this.geometryNode = {
estimatedSpacing: 1.0,
geometry: geometry,
};
}
getLevel(){
return 0;
}
}
class ProfileFakeOctree extends PointCloudTree{
constructor(octree){
super();
this.trueOctree = octree;
this.pcoGeometry = octree.pcoGeometry;
this.points = [];
this.visibleNodes = [];
//this.material = this.trueOctree.material;
this.material = new PointCloudMaterial();
//this.material.copy(this.trueOctree.material);
copyMaterial(this.trueOctree.material, this.material);
this.material.pointSizeType = PointSizeType.FIXED;
this.batchSize = 100 * 1000;
this.currentBatch = null
}
getAttribute(name){
return this.trueOctree.getAttribute(name);
}
dispose(){
for(let node of this.visibleNodes){
node.geometry.dispose();
}
this.visibleNodes = [];
this.currentBatch = null;
this.points = [];
}
addPoints(data){
// since each call to addPoints can deliver very very few points,
// we're going to batch them into larger buffers for efficiency.
if(this.currentBatch === null){
this.currentBatch = this.createNewBatch(data);
}
this.points.push(data);
let updateRange = {
start: this.currentBatch.geometry.drawRange.count,
count: 0
};
let projectedBox = new THREE.Box3();
let truePos = new THREE.Vector3();
for(let i = 0; i < data.numPoints; i++){
if(updateRange.start + updateRange.count >= this.batchSize){
// current batch full, start new batch
for(let key of Object.keys(this.currentBatch.geometry.attributes)){
let attribute = this.currentBatch.geometry.attributes[key];
attribute.updateRange.offset = updateRange.start;
attribute.updateRange.count = updateRange.count;
attribute.needsUpdate = true;
}
this.currentBatch.geometry.computeBoundingBox();
this.currentBatch.geometry.computeBoundingSphere();
this.currentBatch = this.createNewBatch(data);
updateRange = {
start: 0,
count: 0
};
}
truePos.set(
data.data.position[3 * i + 0] + this.trueOctree.position.x,
data.data.position[3 * i + 1] + this.trueOctree.position.y,
data.data.position[3 * i + 2] + this.trueOctree.position.z,
);
let x = data.data.mileage[i];
let y = 0;
let z = truePos.z;
projectedBox.expandByPoint(new THREE.Vector3(x, y, z));
let index = updateRange.start + updateRange.count;
let geometry = this.currentBatch.geometry;
for(let attributeName of Object.keys(data.data)){
let source = data.data[attributeName];
let target = geometry.attributes[attributeName];
let numElements = target.itemSize;
for(let item = 0; item < numElements; item++){
target.array[numElements * index + item] = source[numElements * i + item];
}
}
{
let position = geometry.attributes.position;
position.array[3 * index + 0] = x;
position.array[3 * index + 1] = y;
position.array[3 * index + 2] = z;
}
updateRange.count++;
this.currentBatch.geometry.drawRange.count++;
}
for(let key of Object.keys(this.currentBatch.geometry.attributes)){
let attribute = this.currentBatch.geometry.attributes[key];
attribute.updateRange.offset = updateRange.start;
attribute.updateRange.count = updateRange.count;
attribute.needsUpdate = true;
}
data.projectedBox = projectedBox;
this.projectedBox = this.points.reduce( (a, i) => a.union(i.projectedBox), new THREE.Box3());
}
createNewBatch(data){
let geometry = new THREE.BufferGeometry();
// create new batches with batch_size elements of the same type as the attribute
for(let attributeName of Object.keys(data.data)){
let buffer = data.data[attributeName];
let numElements = buffer.length / data.numPoints; // 3 for pos, 4 for col, 1 for scalars
let constructor = buffer.constructor;
let normalized = false;
if(this.trueOctree.root.sceneNode){
if(this.trueOctree.root.sceneNode.geometry.attributes[attributeName]){
normalized = this.trueOctree.root.sceneNode.geometry.attributes[attributeName].normalized;
}
}
let batchBuffer = new constructor(numElements * this.batchSize);
let bufferAttribute = new THREE.BufferAttribute(batchBuffer, numElements, normalized);
bufferAttribute.potree = {
range: [0, 1],
};
geometry.setAttribute(attributeName, bufferAttribute);
}
geometry.drawRange.start = 0;
geometry.drawRange.count = 0;
let batch = new Batch(geometry, this.material);
this.visibleNodes.push(batch);
return batch;
}
computeVisibilityTextureData(){
let data = new Uint8Array(this.visibleNodes.length * 4);
let offsets = new Map();
for(let i = 0; i < this.visibleNodes.length; i++){
let node = this.visibleNodes[i];
offsets[node] = i;
}
return {
data: data,
offsets: offsets,
};
}
}
export class ProfileWindow extends EventDispatcher {
constructor (viewer) {
super();
this.viewer = viewer;
this.elRoot = $('#profile_window');
this.renderArea = this.elRoot.find('#profileCanvasContainer');
this.svg = d3.select('svg#profileSVG');
this.mouseIsDown = false;
this.projectedBox = new THREE.Box3();
this.pointclouds = new Map();
this.numPoints = 0;
this.lastAddPointsTimestamp = undefined;
this.mouse = new THREE.Vector2(0, 0);
this.scale = new THREE.Vector3(1, 1, 1);
this.autoFitEnabled = true; // completely disable/enable
this.autoFit = false; // internal
let cwIcon = `${exports.resourcePath}/icons/arrow_cw.svg`;
$('#potree_profile_rotate_cw').attr('src', cwIcon);
let ccwIcon = `${exports.resourcePath}/icons/arrow_ccw.svg`;
$('#potree_profile_rotate_ccw').attr('src', ccwIcon);
let forwardIcon = `${exports.resourcePath}/icons/arrow_up.svg`;
$('#potree_profile_move_forward').attr('src', forwardIcon);
let backwardIcon = `${exports.resourcePath}/icons/arrow_down.svg`;
$('#potree_profile_move_backward').attr('src', backwardIcon);
let dxf2DIcon = `${exports.resourcePath}/icons/file_dxf_2d.svg`;
$('#potree_download_dxf2D_icon').attr('src', dxf2DIcon);
let dxf3DIcon = `${exports.resourcePath}/icons/file_dxf_3d.svg`;
$('#potree_download_dxf3D_icon').attr('src', dxf3DIcon);
let csvIcon = `${exports.resourcePath}/icons/file_csv_2d.svg`;
$('#potree_download_csv_icon').attr('src', csvIcon);
let lasIcon = `${exports.resourcePath}/icons/file_las_3d.svg`;
$('#potree_download_las_icon').attr('src', lasIcon);
let closeIcon = `${exports.resourcePath}/icons/close.svg`;
$('#closeProfileContainer').attr("src", closeIcon);
this.initTHREE();
this.initSVG();
this.initListeners();
this.pRenderer = new Renderer(this.renderer);
this.elRoot.i18n();
}
initListeners () {
$(window).resize(() => {
if (this.enabled) {
this.render();
}
});
this.renderArea.mousedown(e => {
this.mouseIsDown = true;
});
this.renderArea.mouseup(e => {
this.mouseIsDown = false;
});
let viewerPickSphereSizeHandler = () => {
let camera = this.viewer.scene.getActiveCamera();
let domElement = this.viewer.renderer.domElement;
let distance = this.viewerPickSphere.position.distanceTo(camera.position);
let pr = Utils.projectedRadius(1, camera, distance, domElement.clientWidth, domElement.clientHeight);
let scale = (10 / pr);
this.viewerPickSphere.scale.set(scale, scale, scale);
};
this.renderArea.mousemove(e => {
if (this.pointclouds.size === 0) {
return;
}
let rect = this.renderArea[0].getBoundingClientRect();
let x = e.clientX - rect.left;
let y = e.clientY - rect.top;
let newMouse = new THREE.Vector2(x, y);
if (this.mouseIsDown) {
// DRAG
this.autoFit = false;
this.lastDrag = new Date().getTime();
let cPos = [this.scaleX.invert(this.mouse.x), this.scaleY.invert(this.mouse.y)];
let ncPos = [this.scaleX.invert(newMouse.x), this.scaleY.invert(newMouse.y)];
this.camera.position.x -= ncPos[0] - cPos[0];
this.camera.position.z -= ncPos[1] - cPos[1];
this.render();
} else if (this.pointclouds.size > 0) {
// FIND HOVERED POINT
let radius = Math.abs(this.scaleX.invert(0) - this.scaleX.invert(40));
let mileage = this.scaleX.invert(newMouse.x);
let elevation = this.scaleY.invert(newMouse.y);
let closest = this.selectPoint(mileage, elevation, radius);
if (closest) {
let point = closest.point;
let position = new Float64Array([
point.position[0] + closest.pointcloud.position.x,
point.position[1] + closest.pointcloud.position.y,
point.position[2] + closest.pointcloud.position.z
]);
this.elRoot.find('#profileSelectionProperties').fadeIn(200);
this.pickSphere.visible = true;
this.pickSphere.scale.set(0.5 * radius, 0.5 * radius, 0.5 * radius);
this.pickSphere.position.set(point.mileage, 0, position[2]);
this.viewerPickSphere.position.set(...position);
if(!this.viewer.scene.scene.children.includes(this.viewerPickSphere)){
this.viewer.scene.scene.add(this.viewerPickSphere);
if(!this.viewer.hasEventListener("update", viewerPickSphereSizeHandler)){
this.viewer.addEventListener("update", viewerPickSphereSizeHandler);
}
}
let info = this.elRoot.find('#profileSelectionProperties');
let html = '<table>';
for (let attributeName of Object.keys(point)) {
let value = point[attributeName];
let attribute = closest.pointcloud.getAttribute(attributeName);
let transform = value => value;
if(attribute && attribute.type.size > 4){
let range = attribute.initialRange;
let scale = 1 / (range[1] - range[0]);
let offset = range[0];
transform = value => value / scale + offset;
}
if (attributeName === 'position') {
let values = [...position].map(v => Utils.addCommas(v.toFixed(3)));
html += `
<tr>
<td>x</td>
<td>${values[0]}</td>
</tr>
<tr>
<td>y</td>
<td>${values[1]}</td>
</tr>
<tr>
<td>z</td>
<td>${values[2]}</td>
</tr>`;
} else if (attributeName === 'rgba') {
html += `
<tr>
<td>${attributeName}</td>
<td>${value.join(', ')}</td>
</tr>`;
} else if (attributeName === 'normal') {
continue;
} else if (attributeName === 'mileage') {
html += `
<tr>
<td>${attributeName}</td>
<td>${value.toFixed(3)}</td>
</tr>`;
} else {
html += `
<tr>
<td>${attributeName}</td>
<td>${transform(value)}</td>
</tr>`;
}
}
html += '</table>';
info.html(html);
this.selectedPoint = point;
} else {
// this.pickSphere.visible = false;
// this.selectedPoint = null;
this.viewer.scene.scene.add(this.viewerPickSphere);
let index = this.viewer.scene.scene.children.indexOf(this.viewerPickSphere);
if(index >= 0){
this.viewer.scene.scene.children.splice(index, 1);
}
this.viewer.removeEventListener("update", viewerPickSphereSizeHandler);
}
this.render();
}
this.mouse.copy(newMouse);
});
let onWheel = e => {
this.autoFit = false;
let delta = 0;
if (e.wheelDelta !== undefined) { // WebKit / Opera / Explorer 9
delta = e.wheelDelta;
} else if (e.detail !== undefined) { // Firefox
delta = -e.detail;
}
let ndelta = Math.sign(delta);
let cPos = [this.scaleX.invert(this.mouse.x), this.scaleY.invert(this.mouse.y)];
if (ndelta > 0) {
// + 10%
this.scale.multiplyScalar(1.1);
} else {
// - 10%
this.scale.multiplyScalar(100 / 110);
}
this.updateScales();
let ncPos = [this.scaleX.invert(this.mouse.x), this.scaleY.invert(this.mouse.y)];
this.camera.position.x -= ncPos[0] - cPos[0];
this.camera.position.z -= ncPos[1] - cPos[1];
this.render();
this.updateScales();
};
$(this.renderArea)[0].addEventListener('mousewheel', onWheel, false);
$(this.renderArea)[0].addEventListener('DOMMouseScroll', onWheel, false); // Firefox
$('#closeProfileContainer').click(() => {
this.hide();
});
let getProfilePoints = (truePosition) => {
let points = new Points();
for(let [pointcloud, entry] of this.pointclouds){
for(let pointSet of entry.points){
let originPos = pointSet.data.position;
let truePointPosition = new Float64Array(originPos);
for(let i = 0; i < pointSet.numPoints; i++){
if (truePosition === true) {
truePointPosition[3 * i + 0] += pointcloud.position.x;
truePointPosition[3 * i + 1] += pointcloud.position.y;
}
truePointPosition[3 * i + 2] += pointcloud.position.z;
}
pointSet.data.position = truePointPosition;
points.add(pointSet);
pointSet.data.position = originPos;
}
}
return points;
};
$('#potree_download_dxf2D_icon').click(() => {
const points = getProfilePoints();
const string = DXFProfileExporter.toString(points, true);
const blob = new Blob([string], {type: "text/string"});
$('#potree_download_profile_dxf2D_link').attr('href', URL.createObjectURL(blob));
});
$('#potree_download_dxf3D_icon').click(() => {
const points = getProfilePoints(true);
const string = DXFProfileExporter.toString(points);
const blob = new Blob([string], {type: "text/string"});
$('#potree_download_profile_dxf3D_link').attr('href', URL.createObjectURL(blob));
});
$('#potree_download_csv_icon').click(() => {
let points = getProfilePoints(true);
let string = CSVExporter.toString(points);
let blob = new Blob([string], {type: "text/string"});
$('#potree_download_profile_ortho_link').attr('href', URL.createObjectURL(blob));
});
$('#potree_download_las_icon').click(() => {
let points = getProfilePoints(true);
let buffer = LASExporter.toLAS(points);
let blob = new Blob([buffer], {type: "application/octet-binary"});
$('#potree_download_profile_link').attr('href', URL.createObjectURL(blob));
});
}
selectPoint (mileage, elevation, radius) {
let closest = {
distance: Infinity,
pointcloud: null,
points: null,
index: null
};
let pointBox = new THREE.Box2(
new THREE.Vector2(mileage - radius, elevation - radius),
new THREE.Vector2(mileage + radius, elevation + radius));
let numTested = 0;
let numSkipped = 0;
let numTestedPoints = 0;
let numSkippedPoints = 0;
for (let [pointcloud, entry] of this.pointclouds) {
for(let points of entry.points){
let collisionBox = new THREE.Box2(
new THREE.Vector2(points.projectedBox.min.x, points.projectedBox.min.z),
new THREE.Vector2(points.projectedBox.max.x, points.projectedBox.max.z)
);
let intersects = collisionBox.intersectsBox(pointBox);
if(!intersects){
numSkipped++;
numSkippedPoints += points.numPoints;
continue;
}
numTested++;
numTestedPoints += points.numPoints
for (let i = 0; i < points.numPoints; i++) {
let m = points.data.mileage[i] - mileage;
let e = points.data.position[3 * i + 2] - elevation + pointcloud.position.z;
let r = Math.sqrt(m * m + e * e);
const withinDistance = r < radius && r < closest.distance;
let unfilteredClass = true;
if(points.data.classification){
const classification = pointcloud.material.classification;
const pointClassID = points.data.classification[i];
const pointClassValue = classification[pointClassID];
if(pointClassValue && (!pointClassValue.visible || pointClassValue.color.w === 0)){
unfilteredClass = false;
}
}
if (withinDistance && unfilteredClass) {
closest = {
distance: r,
pointcloud: pointcloud,
points: points,
index: i
};
}
}
}
}
//console.log(`nodes: ${numTested}, ${numSkipped} || points: ${numTestedPoints}, ${numSkippedPoints}`);
if (closest.distance < Infinity) {
let points = closest.points;
let point = {};
let attributes = Object.keys(points.data);
for (let attribute of attributes) {
let attributeData = points.data[attribute];
let itemSize = attributeData.length / points.numPoints;
let value = attributeData.subarray(itemSize * closest.index, itemSize * closest.index + itemSize);
if (value.length === 1) {
point[attribute] = value[0];
} else {
point[attribute] = value;
}
}
closest.point = point;
return closest;
} else {
return null;
}
}
initTHREE () {
this.renderer = new THREE.WebGLRenderer({alpha: true, premultipliedAlpha: false});
this.renderer.setClearColor(0x000000, 0);
this.renderer.setSize(10, 10);
this.renderer.autoClear = false;
this.renderArea.append($(this.renderer.domElement));
this.renderer.domElement.tabIndex = '2222';
$(this.renderer.domElement).css('width', '100%');
$(this.renderer.domElement).css('height', '100%');
{
let gl = this.renderer.getContext();
if(gl.createVertexArray == null){
let extVAO = gl.getExtension('OES_vertex_array_object');
if(!extVAO){
throw new Error("OES_vertex_array_object extension not supported");
}
gl.createVertexArray = extVAO.createVertexArrayOES.bind(extVAO);
gl.bindVertexArray = extVAO.bindVertexArrayOES.bind(extVAO);
}
}
this.camera = new THREE.OrthographicCamera(-1000, 1000, 1000, -1000, -1000, 1000);
this.camera.up.set(0, 0, 1);
this.camera.rotation.order = "ZXY";
this.camera.rotation.x = Math.PI / 2.0;
this.scene = new THREE.Scene();
this.profileScene = new THREE.Scene();
let sg = new THREE.SphereGeometry(1, 16, 16);
let sm = new THREE.MeshNormalMaterial();
this.pickSphere = new THREE.Mesh(sg, sm);
this.scene.add(this.pickSphere);
this.viewerPickSphere = new THREE.Mesh(sg, sm);
}
initSVG () {
let width = this.renderArea[0].clientWidth;
let height = this.renderArea[0].clientHeight;
let marginLeft = this.renderArea[0].offsetLeft;
this.svg.selectAll('*').remove();
this.scaleX = d3.scale.linear()
.domain([this.camera.left + this.camera.position.x, this.camera.right + this.camera.position.x])
.range([0, width]);
this.scaleY = d3.scale.linear()
.domain([this.camera.bottom + this.camera.position.z, this.camera.top + this.camera.position.z])
.range([height, 0]);
this.xAxis = d3.svg.axis()
.scale(this.scaleX)
.orient('bottom')
.innerTickSize(-height)
.outerTickSize(1)
.tickPadding(10)
.ticks(width / 50);
this.yAxis = d3.svg.axis()
.scale(this.scaleY)
.orient('left')
.innerTickSize(-width)
.outerTickSize(1)
.tickPadding(10)
.ticks(height / 20);
this.elXAxis = this.svg.append('g')
.attr('class', 'x axis')
.attr('transform', `translate(${marginLeft}, ${height})`)
.call(this.xAxis);
this.elYAxis = this.svg.append('g')
.attr('class', 'y axis')
.attr('transform', `translate(${marginLeft}, 0)`)
.call(this.yAxis);
}
addPoints (pointcloud, points) {
if(points.numPoints === 0){
return;
}
let entry = this.pointclouds.get(pointcloud);
if(!entry){
entry = new ProfileFakeOctree(pointcloud);
this.pointclouds.set(pointcloud, entry);
this.profileScene.add(entry);
let materialChanged = () => {
this.render();
};
materialChanged();
pointcloud.material.addEventListener('material_property_changed', materialChanged);
this.addEventListener("on_reset_once", () => {
pointcloud.material.removeEventListener('material_property_changed', materialChanged);
});
}
entry.addPoints(points);
this.projectedBox.union(entry.projectedBox);
if (this.autoFit && this.autoFitEnabled) {
let width = this.renderArea[0].clientWidth;
let height = this.renderArea[0].clientHeight;
let size = this.projectedBox.getSize(new THREE.Vector3());
let sx = width / size.x;
let sy = height / size.z;
let scale = Math.min(sx, sy);
let center = this.projectedBox.getCenter(new THREE.Vector3());
this.scale.set(scale, scale, 1);
this.camera.position.copy(center);
//console.log("camera: ", this.camera.position.toArray().join(", "));
}
//console.log(entry);
this.render();
let numPoints = 0;
for (let [key, value] of this.pointclouds.entries()) {
numPoints += value.points.reduce( (a, i) => a + i.numPoints, 0);
}
$(`#profile_num_points`).html(Utils.addCommas(numPoints));
}
reset () {
this.lastReset = new Date().getTime();
this.dispatchEvent({type: "on_reset_once"});
this.removeEventListeners("on_reset_once");
this.autoFit = true;
this.projectedBox = new THREE.Box3();
for(let [key, entry] of this.pointclouds){
entry.dispose();
}
this.pointclouds.clear();
this.mouseIsDown = false;
this.mouse.set(0, 0);
if(this.autoFitEnabled){
this.scale.set(1, 1, 1);
}
this.pickSphere.visible = false;
this.elRoot.find('#profileSelectionProperties').hide();
this.render();
}
show () {
this.elRoot.fadeIn();
this.enabled = true;
}
hide () {
this.elRoot.fadeOut();
this.enabled = false;
}
updateScales () {
let width = this.renderArea[0].clientWidth;
let height = this.renderArea[0].clientHeight;
let left = (-width / 2) / this.scale.x;
let right = (+width / 2) / this.scale.x;
let top = (+height / 2) / this.scale.y;
let bottom = (-height / 2) / this.scale.y;
this.camera.left = left;
this.camera.right = right;
this.camera.top = top;
this.camera.bottom = bottom;
this.camera.updateProjectionMatrix();
this.scaleX.domain([this.camera.left + this.camera.position.x, this.camera.right + this.camera.position.x])
.range([0, width]);
this.scaleY.domain([this.camera.bottom + this.camera.position.z, this.camera.top + this.camera.position.z])
.range([height, 0]);
let marginLeft = this.renderArea[0].offsetLeft;
this.xAxis.scale(this.scaleX)
.orient('bottom')
.innerTickSize(-height)
.outerTickSize(1)
.tickPadding(10)
.ticks(width / 50);
this.yAxis.scale(this.scaleY)
.orient('left')
.innerTickSize(-width)
.outerTickSize(1)
.tickPadding(10)
.ticks(height / 20);
this.elXAxis
.attr('transform', `translate(${marginLeft}, ${height})`)
.call(this.xAxis);
this.elYAxis
.attr('transform', `translate(${marginLeft}, 0)`)
.call(this.yAxis);
}
requestScaleUpdate(){
let threshold = 100;
let allowUpdate = ((this.lastReset === undefined) || (this.lastScaleUpdate === undefined))
|| ((new Date().getTime() - this.lastReset) > threshold && (new Date().getTime() - this.lastScaleUpdate) > threshold);
if(allowUpdate){
this.updateScales();
this.lastScaleUpdate = new Date().getTime();
this.scaleUpdatePending = false;
}else if(!this.scaleUpdatePending) {
setTimeout(this.requestScaleUpdate.bind(this), 100);
this.scaleUpdatePending = true;
}
}
render () {
let width = this.renderArea[0].clientWidth;
let height = this.renderArea[0].clientHeight;
let {renderer, pRenderer, camera, profileScene, scene} = this;
let {scaleX, pickSphere} = this;
renderer.setSize(width, height);
renderer.setClearColor(0x000000, 0);
renderer.clear(true, true, false);
for(let pointcloud of this.pointclouds.keys()){
let source = pointcloud.material;
let target = this.pointclouds.get(pointcloud).material;
copyMaterial(source, target);
target.size = 2;
}
pRenderer.render(profileScene, camera, null);
let radius = Math.abs(scaleX.invert(0) - scaleX.invert(5));
if (radius === 0) {
pickSphere.visible = false;
} else {
pickSphere.scale.set(radius, radius, radius);
pickSphere.visible = true;
}
renderer.render(scene, camera);
this.requestScaleUpdate();
}
};
export class ProfileWindowController {
constructor (viewer) {
this.viewer = viewer;
this.profileWindow = viewer.profileWindow;
this.profile = null;
this.numPoints = 0;
this.threshold = 60 * 1000;
this.rotateAmount = 10;
this.scheduledRecomputeTime = null;
this.enabled = true;
this.requests = [];
this._recompute = () => { this.recompute(); };
this.viewer.addEventListener("scene_changed", e => {
e.oldScene.removeEventListener("pointcloud_added", this._recompute);
e.scene.addEventListener("pointcloud_added", this._recompute);
});
this.viewer.scene.addEventListener("pointcloud_added", this._recompute);
$("#potree_profile_rotate_amount").val(parseInt(this.rotateAmount));
$("#potree_profile_rotate_amount").on("input", (e) => {
const str = $("#potree_profile_rotate_amount").val();
if(!isNaN(str)){
const value = parseFloat(str);
this.rotateAmount = value;
$("#potree_profile_rotate_amount").css("background-color", "")
}else{
$("#potree_profile_rotate_amount").css("background-color", "#ff9999")
}
});
const rotate = (radians) => {
const profile = this.profile;
const points = profile.points;
const start = points[0];
const end = points[points.length - 1];
const center = start.clone().add(end).multiplyScalar(0.5);
const mMoveOrigin = new THREE.Matrix4().makeTranslation(-center.x, -center.y, -center.z);
const mRotate = new THREE.Matrix4().makeRotationZ(radians);
const mMoveBack = new THREE.Matrix4().makeTranslation(center.x, center.y, center.z);
//const transform = mMoveOrigin.multiply(mRotate).multiply(mMoveBack);
const transform = mMoveBack.multiply(mRotate).multiply(mMoveOrigin);
const rotatedPoints = points.map( point => point.clone().applyMatrix4(transform) );
this.profileWindow.autoFitEnabled = false;
for(let i = 0; i < points.length; i++){
profile.setPosition(i, rotatedPoints[i]);
}
}
$("#potree_profile_rotate_cw").click( () => {
const radians = THREE.Math.degToRad(this.rotateAmount);
rotate(-radians);
});
$("#potree_profile_rotate_ccw").click( () => {
const radians = THREE.Math.degToRad(this.rotateAmount);
rotate(radians);
});
$("#potree_profile_move_forward").click( () => {
const profile = this.profile;
const points = profile.points;
const start = points[0];
const end = points[points.length - 1];
const dir = end.clone().sub(start).normalize();
const up = new THREE.Vector3(0, 0, 1);
const forward = up.cross(dir);
const move = forward.clone().multiplyScalar(profile.width / 2);
this.profileWindow.autoFitEnabled = false;
for(let i = 0; i < points.length; i++){
profile.setPosition(i, points[i].clone().add(move));
}
});
$("#potree_profile_move_backward").click( () => {
const profile = this.profile;
const points = profile.points;
const start = points[0];
const end = points[points.length - 1];
const dir = end.clone().sub(start).normalize();
const up = new THREE.Vector3(0, 0, 1);
const forward = up.cross(dir);
const move = forward.clone().multiplyScalar(-profile.width / 2);
this.profileWindow.autoFitEnabled = false;
for(let i = 0; i < points.length; i++){
profile.setPosition(i, points[i].clone().add(move));
}
});
}
setProfile (profile) {
if (this.profile !== null && this.profile !== profile) {
this.profile.removeEventListener('marker_moved', this._recompute);
this.profile.removeEventListener('marker_added', this._recompute);
this.profile.removeEventListener('marker_removed', this._recompute);
this.profile.removeEventListener('width_changed', this._recompute);
}
this.profile = profile;
{
this.profile.addEventListener('marker_moved', this._recompute);
this.profile.addEventListener('marker_added', this._recompute);
this.profile.addEventListener('marker_removed', this._recompute);
this.profile.addEventListener('width_changed', this._recompute);
}
this.recompute();
}
reset () {
this.profileWindow.reset();
this.numPoints = 0;
if (this.profile) {
for (let request of this.requests) {
request.cancel();
}
}
}
progressHandler (pointcloud, progress) {
for (let segment of progress.segments) {
this.profileWindow.addPoints(pointcloud, segment.points);
this.numPoints += segment.points.numPoints;
}
}
cancel () {
for (let request of this.requests) {
request.cancel();
// request.finishLevelThenCancel();
}
this.requests = [];
};
finishLevelThenCancel(){
for (let request of this.requests) {
request.finishLevelThenCancel();
}
this.requests = [];
}
recompute () {
if (!this.profile) {
return;
}
if (this.scheduledRecomputeTime !== null && this.scheduledRecomputeTime > new Date().getTime()) {
return;
} else {
this.scheduledRecomputeTime = new Date().getTime() + 100;
}
this.scheduledRecomputeTime = null;
this.reset();
for (let pointcloud of this.viewer.scene.pointclouds.filter(p => p.visible)) {
let request = pointcloud.getPointsInProfile(this.profile, null, {
'onProgress': (event) => {
if (!this.enabled) {
return;
}
this.progressHandler(pointcloud, event.points);
if (this.numPoints > this.threshold) {
this.finishLevelThenCancel();
}
},
'onFinish': (event) => {
if (!this.enabled) {
}
},
'onCancel': () => {
if (!this.enabled) {
}
}
});
this.requests.push(request);
}
}
};