From 1dc818b3221c89b61cdbd7084b08beb3037ba6c2 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Thu, 4 Feb 2021 14:58:54 -0500 Subject: [PATCH] Ability to measure with textured mesh overlayed --- app/static/app/js/ModelView.jsx | 26 +- .../js/vendor/potree/build/potree/potree.js | 5836 ++++++++--------- .../vendor/potree/build/potree/sidebar.html | 4 +- package.json | 1 - 4 files changed, 2932 insertions(+), 2935 deletions(-) diff --git a/app/static/app/js/ModelView.jsx b/app/static/app/js/ModelView.jsx index 73a36127..e5fcf551 100644 --- a/app/static/app/js/ModelView.jsx +++ b/app/static/app/js/ModelView.jsx @@ -6,7 +6,6 @@ import AssetDownloadButtons from './components/AssetDownloadButtons'; import Standby from './components/Standby'; import ShareButton from './components/ShareButton'; import ImagePopup from './components/ImagePopup'; -import epsg from 'epsg'; import PropTypes from 'prop-types'; import * as THREE from 'THREE'; import $ from 'jquery'; @@ -27,6 +26,9 @@ class TexturedModelMenu extends React.Component{ this.state = { showTexturedModel: false } + + // Translation for sidebar.html + _("Cameras"); } handleClick = (e) => { @@ -194,7 +196,7 @@ class ModelView extends React.Component { viewer.setEDLEnabled(true); viewer.setFOV(60); viewer.setPointBudget(1*1000*1000); - viewer.setEDLEnabled(false); // Temporary fix: https://github.com/OpenDroneMap/WebODM/issues/873 + viewer.setEDLEnabled(true); viewer.loadSettingsFromURL(); viewer.loadGUI(() => { @@ -377,9 +379,14 @@ class ModelView extends React.Component { } setPointCloudsVisible = (flag) => { - for(let pointcloud of viewer.scene.pointclouds){ - pointcloud.visible = flag; - } + viewer.setEDLEnabled(true); + + // Using opacity we can still perform measurements + viewer.setEDLOpacity(flag ? 1 : 0); + + // for(let pointcloud of viewer.scene.pointclouds){ + // pointcloud.visible = flag; + // } } toggleCameras(e){ @@ -487,15 +494,6 @@ class ModelView extends React.Component { } $(function(){ - // Add more proj definitions - const defs = []; - for (let k in epsg){ - if (epsg[k]){ - defs.push([k, epsg[k]]); - } - } - window.proj4.defs(defs); - // Use gettext for translations const oldInit = i18n.init; i18n.addPostProcessor("gettext", function(v, k, opts){ diff --git a/app/static/app/js/vendor/potree/build/potree/potree.js b/app/static/app/js/vendor/potree/build/potree/potree.js index 709679d4..5849bd12 100644 --- a/app/static/app/js/vendor/potree/build/potree/potree.js +++ b/app/static/app/js/vendor/potree/build/potree/potree.js @@ -64312,1159 +64312,1159 @@ void main() { //Potree.workerPool = new Potree.WorkerPool(); - function createPointcloudData(pointcloud) { - - let material = pointcloud.material; - - let ranges = []; - - for(let [name, value] of material.ranges){ - ranges.push({ - name: name, - value: value, - }); - } - - if(typeof material.elevationRange[0] === "number"){ - ranges.push({ - name: "elevationRange", - value: material.elevationRange, - }); - } - if(typeof material.intensityRange[0] === "number"){ - ranges.push({ - name: "intensityRange", - value: material.intensityRange, - }); - } - - let pointSizeTypeName = Object.entries(Potree.PointSizeType).find(e => e[1] === material.pointSizeType)[0]; - - let jsonMaterial = { - activeAttributeName: material.activeAttributeName, - ranges: ranges, - size: material.size, - minSize: material.minSize, - pointSizeType: pointSizeTypeName, - matcap: material.matcap, - }; - - const pcdata = { - name: pointcloud.name, - url: pointcloud.pcoGeometry.url, - position: pointcloud.position.toArray(), - rotation: pointcloud.rotation.toArray(), - scale: pointcloud.scale.toArray(), - material: jsonMaterial, - }; - - return pcdata; - } - - function createProfileData(profile){ - const data = { - uuid: profile.uuid, - name: profile.name, - points: profile.points.map(p => p.toArray()), - height: profile.height, - width: profile.width, - }; - - return data; - } - - function createVolumeData(volume){ - const data = { - uuid: volume.uuid, - type: volume.constructor.name, - name: volume.name, - position: volume.position.toArray(), - rotation: volume.rotation.toArray(), - scale: volume.scale.toArray(), - visible: volume.visible, - clip: volume.clip, - }; - - return data; - } - - function createCameraAnimationData(animation){ - - const controlPoints = animation.controlPoints.map( cp => { - const cpdata = { - position: cp.position.toArray(), - target: cp.target.toArray(), - }; - - return cpdata; - }); - - const data = { - uuid: animation.uuid, - name: animation.name, - duration: animation.duration, - t: animation.t, - curveType: animation.curveType, - visible: animation.visible, - controlPoints: controlPoints, - }; - - return data; - } - - function createMeasurementData(measurement){ - - const data = { - uuid: measurement.uuid, - name: measurement.name, - points: measurement.points.map(p => p.position.toArray()), - showDistances: measurement.showDistances, - showCoordinates: measurement.showCoordinates, - showArea: measurement.showArea, - closed: measurement.closed, - showAngles: measurement.showAngles, - showHeight: measurement.showHeight, - showCircle: measurement.showCircle, - showAzimuth: measurement.showAzimuth, - showEdges: measurement.showEdges, - color: measurement.color.toArray(), - }; - - return data; - } - - function createOrientedImagesData(images){ - const data = { - cameraParamsPath: images.cameraParamsPath, - imageParamsPath: images.imageParamsPath, - }; - - return data; - } - - function createGeopackageData(geopackage){ - const data = { - path: geopackage.path, - }; - - return data; - } - - function createAnnotationData(annotation){ - - const data = { - uuid: annotation.uuid, - title: annotation.title.toString(), - description: annotation.description, - position: annotation.position.toArray(), - offset: annotation.offset.toArray(), - children: [], - }; - - if(annotation.cameraPosition){ - data.cameraPosition = annotation.cameraPosition.toArray(); - } - - if(annotation.cameraTarget){ - data.cameraTarget = annotation.cameraTarget.toArray(); - } - - if(typeof annotation.radius !== "undefined"){ - data.radius = annotation.radius; - } - - return data; - } - - function createAnnotationsData(viewer){ - - const map = new Map(); - - viewer.scene.annotations.traverseDescendants(a => { - const aData = createAnnotationData(a); - - map.set(a, aData); - }); - - for(const [annotation, data] of map){ - for(const child of annotation.children){ - const childData = map.get(child); - data.children.push(childData); - } - } - - const annotations = viewer.scene.annotations.children.map(a => map.get(a)); - - return annotations; - } - - function createSettingsData(viewer){ - return { - pointBudget: viewer.getPointBudget(), - fov: viewer.getFOV(), - edlEnabled: viewer.getEDLEnabled(), - edlRadius: viewer.getEDLRadius(), - edlStrength: viewer.getEDLStrength(), - background: viewer.getBackground(), - minNodeSize: viewer.getMinNodeSize(), - showBoundingBoxes: viewer.getShowBoundingBox(), - }; - } - - function createSceneContentData(viewer){ - - const data = []; - - const potreeObjects = []; - - viewer.scene.scene.traverse(node => { - if(node.potree){ - potreeObjects.push(node); - } - }); - - for(const object of potreeObjects){ - - if(object.potree.file){ - const saveObject = { - file: object.potree.file, - }; - - data.push(saveObject); - } - - - } - - - return data; - } - - function createViewData(viewer){ - const view = viewer.scene.view; - - const data = { - position: view.position.toArray(), - target: view.getPivot().toArray(), - }; - - return data; - } - - function createClassificationData(viewer){ - const classifications = viewer.classifications; - - const data = classifications; - - return data; - } - - function saveProject(viewer) { - - const scene = viewer.scene; - - const data = { - type: "Potree", - version: 1.7, - settings: createSettingsData(viewer), - view: createViewData(viewer), - classification: createClassificationData(viewer), - pointclouds: scene.pointclouds.map(createPointcloudData), - measurements: scene.measurements.map(createMeasurementData), - volumes: scene.volumes.map(createVolumeData), - cameraAnimations: scene.cameraAnimations.map(createCameraAnimationData), - profiles: scene.profiles.map(createProfileData), - annotations: createAnnotationsData(viewer), - orientedImages: scene.orientedImages.map(createOrientedImagesData), - geopackages: scene.geopackages.map(createGeopackageData), - // objects: createSceneContentData(viewer), - }; - - return data; + function createPointcloudData(pointcloud) { + + let material = pointcloud.material; + + let ranges = []; + + for(let [name, value] of material.ranges){ + ranges.push({ + name: name, + value: value, + }); + } + + if(typeof material.elevationRange[0] === "number"){ + ranges.push({ + name: "elevationRange", + value: material.elevationRange, + }); + } + if(typeof material.intensityRange[0] === "number"){ + ranges.push({ + name: "intensityRange", + value: material.intensityRange, + }); + } + + let pointSizeTypeName = Object.entries(Potree.PointSizeType).find(e => e[1] === material.pointSizeType)[0]; + + let jsonMaterial = { + activeAttributeName: material.activeAttributeName, + ranges: ranges, + size: material.size, + minSize: material.minSize, + pointSizeType: pointSizeTypeName, + matcap: material.matcap, + }; + + const pcdata = { + name: pointcloud.name, + url: pointcloud.pcoGeometry.url, + position: pointcloud.position.toArray(), + rotation: pointcloud.rotation.toArray(), + scale: pointcloud.scale.toArray(), + material: jsonMaterial, + }; + + return pcdata; } - class ControlPoint{ - - constructor(){ - this.position = new Vector3(0, 0, 0); - this.target = new Vector3(0, 0, 0); - this.positionHandle = null; - this.targetHandle = null; - } - - }; - - - - class CameraAnimation extends EventDispatcher{ - - constructor(viewer){ - super(); - - this.viewer = viewer; - - this.selectedElement = null; - - this.controlPoints = []; - - this.uuid = MathUtils.generateUUID(); - - this.node = new Object3D(); - this.node.name = "camera animation"; - this.viewer.scene.scene.add(this.node); - - this.frustum = this.createFrustum(); - this.node.add(this.frustum); - - this.name = "Camera Animation"; - this.duration = 5; - this.t = 0; - // "centripetal", "chordal", "catmullrom" - this.curveType = "centripetal"; - this.visible = true; - - this.createUpdateHook(); - this.createPath(); - } - - static defaultFromView(viewer){ - const animation = new CameraAnimation(viewer); - - const camera = viewer.scene.getActiveCamera(); - const target = viewer.scene.view.getPivot(); - - const cpCenter = new Vector3( - 0.3 * camera.position.x + 0.7 * target.x, - 0.3 * camera.position.y + 0.7 * target.y, - 0.3 * camera.position.z + 0.7 * target.z, - ); - - const targetCenter = new Vector3( - 0.05 * camera.position.x + 0.95 * target.x, - 0.05 * camera.position.y + 0.95 * target.y, - 0.05 * camera.position.z + 0.95 * target.z, - ); - - const r = camera.position.distanceTo(target) * 0.3; - - //const dir = target.clone().sub(camera.position).normalize(); - const angle = Utils.computeAzimuth(camera.position, target); - - const n = 5; - for(let i = 0; i < n; i++){ - let u = 1.5 * Math.PI * (i / n) + angle; - - const dx = r * Math.cos(u); - const dy = r * Math.sin(u); - - const cpPos = [ - cpCenter.x + dx, - cpCenter.y + dy, - cpCenter.z, - ]; - - const targetPos = [ - targetCenter.x + dx * 0.1, - targetCenter.y + dy * 0.1, - targetCenter.z, - ]; - - const cp = animation.createControlPoint(); - cp.position.set(...cpPos); - cp.target.set(...targetPos); - } - - return animation; - } - - createUpdateHook(){ - const viewer = this.viewer; - - viewer.addEventListener("update", () => { - - const camera = viewer.scene.getActiveCamera(); - const {width, height} = viewer.renderer.getSize(new Vector2()); - - this.node.visible = this.visible; - - for(const cp of this.controlPoints){ - - { // position - const projected = cp.position.clone().project(camera); - - const visible = this.visible && (projected.z < 1 && projected.z > -1); - - if(visible){ - const x = width * (projected.x * 0.5 + 0.5); - const y = height - height * (projected.y * 0.5 + 0.5); - - cp.positionHandle.svg.style.left = x - cp.positionHandle.svg.clientWidth / 2; - cp.positionHandle.svg.style.top = y - cp.positionHandle.svg.clientHeight / 2; - cp.positionHandle.svg.style.display = ""; - }else { - cp.positionHandle.svg.style.display = "none"; - } - } - - { // target - const projected = cp.target.clone().project(camera); - - const visible = this.visible && (projected.z < 1 && projected.z > -1); - - if(visible){ - const x = width * (projected.x * 0.5 + 0.5); - const y = height - height * (projected.y * 0.5 + 0.5); - - cp.targetHandle.svg.style.left = x - cp.targetHandle.svg.clientWidth / 2; - cp.targetHandle.svg.style.top = y - cp.targetHandle.svg.clientHeight / 2; - cp.targetHandle.svg.style.display = ""; - }else { - cp.targetHandle.svg.style.display = "none"; - } - } - - } - - this.line.material.resolution.set(width, height); - - this.updatePath(); - - { // frustum - const frame = this.at(this.t); - const frustum = this.frustum; - - frustum.position.copy(frame.position); - frustum.lookAt(...frame.target.toArray()); - frustum.scale.set(20, 20, 20); - - frustum.material.resolution.set(width, height); - } - - }); - } - - createControlPoint(index){ - - if(index === undefined){ - index = this.controlPoints.length; - } - - const cp = new ControlPoint(); - - - if(this.controlPoints.length >= 2 && index === 0){ - const cp1 = this.controlPoints[0]; - const cp2 = this.controlPoints[1]; - - const dir = cp1.position.clone().sub(cp2.position).multiplyScalar(0.5); - cp.position.copy(cp1.position).add(dir); - - const tDir = cp1.target.clone().sub(cp2.target).multiplyScalar(0.5); - cp.target.copy(cp1.target).add(tDir); - }else if(this.controlPoints.length >= 2 && index === this.controlPoints.length){ - const cp1 = this.controlPoints[this.controlPoints.length - 2]; - const cp2 = this.controlPoints[this.controlPoints.length - 1]; - - const dir = cp2.position.clone().sub(cp1.position).multiplyScalar(0.5); - cp.position.copy(cp1.position).add(dir); - - const tDir = cp2.target.clone().sub(cp1.target).multiplyScalar(0.5); - cp.target.copy(cp2.target).add(tDir); - }else if(this.controlPoints.length >= 2){ - const cp1 = this.controlPoints[index - 1]; - const cp2 = this.controlPoints[index]; - - cp.position.copy(cp1.position.clone().add(cp2.position).multiplyScalar(0.5)); - cp.target.copy(cp1.target.clone().add(cp2.target).multiplyScalar(0.5)); - } - - // cp.position.copy(viewer.scene.view.position); - // cp.target.copy(viewer.scene.view.getPivot()); - - cp.positionHandle = this.createHandle(cp.position); - cp.targetHandle = this.createHandle(cp.target); - - this.controlPoints.splice(index, 0, cp); - - this.dispatchEvent({ - type: "controlpoint_added", - controlpoint: cp, - }); - - return cp; - } - - removeControlPoint(cp){ - this.controlPoints = this.controlPoints.filter(_cp => _cp !== cp); - - this.dispatchEvent({ - type: "controlpoint_removed", - controlpoint: cp, - }); - - cp.positionHandle.svg.remove(); - cp.targetHandle.svg.remove(); - - // TODO destroy cp - } - - createPath(){ - - { // position - const geometry = new LineGeometry(); - - let material = new LineMaterial({ - color: 0x00ff00, - dashSize: 5, - gapSize: 2, - linewidth: 2, - resolution: new Vector2(1000, 1000), - }); - - const line = new Line2(geometry, material); - - this.line = line; - this.node.add(line); - } - - { // target - const geometry = new LineGeometry(); - - let material = new LineMaterial({ - color: 0x0000ff, - dashSize: 5, - gapSize: 2, - linewidth: 2, - resolution: new Vector2(1000, 1000), - }); - - const line = new Line2(geometry, material); - - this.targetLine = line; - this.node.add(line); - } - } - - createFrustum(){ - - const f = 0.3; - - const positions = [ - 0, 0, 0, - -f, -f, +1, - - 0, 0, 0, - f, -f, +1, - - 0, 0, 0, - f, f, +1, - - 0, 0, 0, - -f, f, +1, - - -f, -f, +1, - f, -f, +1, - - f, -f, +1, - f, f, +1, - - f, f, +1, - -f, f, +1, - - -f, f, +1, - -f, -f, +1, - ]; - - const geometry = new LineGeometry(); - - geometry.setPositions(positions); - geometry.verticesNeedUpdate = true; - geometry.computeBoundingSphere(); - - let material = new LineMaterial({ - color: 0xff0000, - linewidth: 2, - resolution: new Vector2(1000, 1000), - }); - - const line = new Line2(geometry, material); - line.computeLineDistances(); - - return line; - } - - updatePath(){ - - { // positions - const positions = this.controlPoints.map(cp => cp.position); - const first = positions[0]; - - const curve = new CatmullRomCurve3(positions); - curve.curveType = this.curveType; - - const n = 100; - - const curvePositions = []; - for(let k = 0; k <= n; k++){ - const t = k / n; - - const position = curve.getPoint(t).sub(first); - - curvePositions.push(position.x, position.y, position.z); - } - - this.line.geometry.setPositions(curvePositions); - this.line.geometry.verticesNeedUpdate = true; - this.line.geometry.computeBoundingSphere(); - this.line.position.copy(first); - this.line.computeLineDistances(); - - this.cameraCurve = curve; - } - - { // targets - const positions = this.controlPoints.map(cp => cp.target); - const first = positions[0]; - - const curve = new CatmullRomCurve3(positions); - curve.curveType = this.curveType; - - const n = 100; - - const curvePositions = []; - for(let k = 0; k <= n; k++){ - const t = k / n; - - const position = curve.getPoint(t).sub(first); - - curvePositions.push(position.x, position.y, position.z); - } - - this.targetLine.geometry.setPositions(curvePositions); - this.targetLine.geometry.verticesNeedUpdate = true; - this.targetLine.geometry.computeBoundingSphere(); - this.targetLine.position.copy(first); - this.targetLine.computeLineDistances(); - - this.targetCurve = curve; - } - } - - at(t){ - - if(t > 1){ - t = 1; - }else if(t < 0){ - t = 0; - } - - const camPos = this.cameraCurve.getPointAt(t); - const target = this.targetCurve.getPointAt(t); - - const frame = { - position: camPos, - target: target, - }; - - return frame; - } - - set(t){ - this.t = t; - } - - createHandle(vector){ - - const svgns = "http://www.w3.org/2000/svg"; - const svg = document.createElementNS(svgns, "svg"); - - svg.setAttribute("width", "2em"); - svg.setAttribute("height", "2em"); - svg.setAttribute("position", "absolute"); - - svg.style.left = "50px"; - svg.style.top = "50px"; - svg.style.position = "absolute"; - svg.style.zIndex = "10000"; - - const circle = document.createElementNS(svgns, 'circle'); - circle.setAttributeNS(null, 'cx', "1em"); - circle.setAttributeNS(null, 'cy', "1em"); - circle.setAttributeNS(null, 'r', "0.5em"); - circle.setAttributeNS(null, 'style', 'fill: red; stroke: black; stroke-width: 0.2em;' ); - svg.appendChild(circle); - - - const element = this.viewer.renderer.domElement.parentElement; - element.appendChild(svg); - - - const startDrag = (evt) => { - this.selectedElement = svg; - - document.addEventListener("mousemove", drag); - }; - - const endDrag = (evt) => { - this.selectedElement = null; - - document.removeEventListener("mousemove", drag); - }; - - const drag = (evt) => { - if (this.selectedElement) { - evt.preventDefault(); - - const rect = viewer.renderer.domElement.getBoundingClientRect(); - - const x = evt.clientX - rect.x; - const y = evt.clientY - rect.y; - - const {width, height} = this.viewer.renderer.getSize(new Vector2()); - const camera = this.viewer.scene.getActiveCamera(); - //const cp = this.controlPoints.find(cp => cp.handle.svg === svg); - const projected = vector.clone().project(camera); - - projected.x = ((x / width) - 0.5) / 0.5; - projected.y = (-(y - height) / height - 0.5) / 0.5; - - const unprojected = projected.clone().unproject(camera); - vector.set(unprojected.x, unprojected.y, unprojected.z); - - - } - }; - - svg.addEventListener('mousedown', startDrag); - svg.addEventListener('mouseup', endDrag); - - const handle = { - svg: svg, - }; - - return handle; - } - - setVisible(visible){ - this.node.visible = visible; - - const display = visible ? "" : "none"; - - for(const cp of this.controlPoints){ - cp.positionHandle.svg.style.display = display; - cp.targetHandle.svg.style.display = display; - } - - this.visible = visible; - } - - setDuration(duration){ - this.duration = duration; - } - - getDuration(duration){ - return this.duration; - } - - play(){ - - const tStart = performance.now(); - const duration = this.duration; - - const originalyVisible = this.visible; - this.setVisible(false); - - const onUpdate = (delta) => { - - let tNow = performance.now(); - let elapsed = (tNow - tStart) / 1000; - let t = elapsed / duration; - - this.set(t); - - const frame = this.at(t); - - viewer.scene.view.position.copy(frame.position); - viewer.scene.view.lookAt(frame.target); - - - if(t > 1){ - this.setVisible(originalyVisible); - - this.viewer.removeEventListener("update", onUpdate); - } - - }; - - this.viewer.addEventListener("update", onUpdate); - - } - + function createProfileData(profile){ + const data = { + uuid: profile.uuid, + name: profile.name, + points: profile.points.map(p => p.toArray()), + height: profile.height, + width: profile.width, + }; + + return data; } - function loadPointCloud(viewer, data){ - - let loadMaterial = (target) => { - - if(data.material){ - - if(data.material.activeAttributeName != null){ - target.activeAttributeName = data.material.activeAttributeName; - } - - if(data.material.ranges != null){ - for(let range of data.material.ranges){ - - if(range.name === "elevationRange"){ - target.elevationRange = range.value; - }else if(range.name === "intensityRange"){ - target.intensityRange = range.value; - }else { - target.setRange(range.name, range.value); - } - - } - } - - if(data.material.size != null){ - target.size = data.material.size; - } - - if(data.material.minSize != null){ - target.minSize = data.material.minSize; - } - - if(data.material.pointSizeType != null){ - target.pointSizeType = PointSizeType[data.material.pointSizeType]; - } - - if(data.material.matcap != null){ - target.matcap = data.material.matcap; - } - - }else if(data.activeAttributeName != null){ - target.activeAttributeName = data.activeAttributeName; - }else { - // no material data - } - - }; - - const promise = new Promise((resolve) => { - - const names = viewer.scene.pointclouds.map(p => p.name); - const alreadyExists = names.includes(data.name); - - if(alreadyExists){ - resolve(); - return; - } - - Potree.loadPointCloud(data.url, data.name, (e) => { - const {pointcloud} = e; - - pointcloud.position.set(...data.position); - pointcloud.rotation.set(...data.rotation); - pointcloud.scale.set(...data.scale); - - loadMaterial(pointcloud.material); - - viewer.scene.addPointCloud(pointcloud); - - resolve(pointcloud); - }); - }); - - return promise; - } - - function loadMeasurement(viewer, data){ - - const duplicate = viewer.scene.measurements.find(measure => measure.uuid === data.uuid); - if(duplicate){ - return; - } - - const measure = new Measure(); - - measure.uuid = data.uuid; - measure.name = data.name; - measure.showDistances = data.showDistances; - measure.showCoordinates = data.showCoordinates; - measure.showArea = data.showArea; - measure.closed = data.closed; - measure.showAngles = data.showAngles; - measure.showHeight = data.showHeight; - measure.showCircle = data.showCircle; - measure.showAzimuth = data.showAzimuth; - measure.showEdges = data.showEdges; - // color - - for(const point of data.points){ - const pos = new Vector3(...point); - measure.addMarker(pos); - } - - viewer.scene.addMeasurement(measure); - - } - - function loadVolume(viewer, data){ - - const duplicate = viewer.scene.volumes.find(volume => volume.uuid === data.uuid); - if(duplicate){ - return; - } - - let volume = new Potree[data.type]; - - volume.uuid = data.uuid; - volume.name = data.name; - volume.position.set(...data.position); - volume.rotation.set(...data.rotation); - volume.scale.set(...data.scale); - volume.visible = data.visible; - volume.clip = data.clip; - - viewer.scene.addVolume(volume); - } - - function loadCameraAnimation(viewer, data){ - - const duplicate = viewer.scene.cameraAnimations.find(a => a.uuid === data.uuid); - if(duplicate){ - return; - } - - const animation = new CameraAnimation(viewer); - - animation.uuid = data.uuid; - animation.name = data.name; - animation.duration = data.duration; - animation.t = data.t; - animation.curveType = data.curveType; - animation.visible = data.visible; - animation.controlPoints = []; - - for(const cpdata of data.controlPoints){ - const cp = animation.createControlPoint(); - - cp.position.set(...cpdata.position); - cp.target.set(...cpdata.target); - } - - viewer.scene.addCameraAnimation(animation); - } - - function loadOrientedImages(viewer, images){ - - const {cameraParamsPath, imageParamsPath} = images; - - const duplicate = viewer.scene.orientedImages.find(i => i.imageParamsPath === imageParamsPath); - if(duplicate){ - return; - } - - Potree.OrientedImageLoader.load(cameraParamsPath, imageParamsPath, viewer).then( images => { - viewer.scene.addOrientedImages(images); - }); - - } - - function loadGeopackage(viewer, geopackage){ - - const path = geopackage.path; - - const duplicate = viewer.scene.geopackages.find(i => i.path === path); - if(duplicate){ - return; - } - - const projection = viewer.getProjection(); - - proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"); - proj4.defs("pointcloud", projection); - const transform = proj4("WGS84", "pointcloud"); - const params = { - transform: transform, - }; - - Potree.GeoPackageLoader.loadUrl(path, params).then(data => { - viewer.scene.addGeopackage(data); - }); - - - } - - function loadSettings(viewer, data){ - if(!data){ - return; - } - - viewer.setPointBudget(data.pointBudget); - viewer.setFOV(data.fov); - viewer.setEDLEnabled(data.edlEnabled); - viewer.setEDLRadius(data.edlRadius); - viewer.setEDLStrength(data.edlStrength); - viewer.setBackground(data.background); - viewer.setMinNodeSize(data.minNodeSize); - viewer.setShowBoundingBox(data.showBoundingBoxes); - } - - function loadView(viewer, view){ - viewer.scene.view.position.set(...view.position); - viewer.scene.view.lookAt(...view.target); - } - - function loadAnnotationItem(item){ - - const annotation = new Annotation({ - position: item.position, - title: item.title, - cameraPosition: item.cameraPosition, - cameraTarget: item.cameraTarget, - }); - - - annotation.description = item.description; - annotation.uuid = item.uuid; - - if(item.offset){ - annotation.offset.set(...item.offset); - } - - return annotation; - } - - function loadAnnotations(viewer, data){ - - if(!data){ - return; - } - - const findDuplicate = (item) => { - - let duplicate = null; - - viewer.scene.annotations.traverse( a => { - if(a.uuid === item.uuid){ - duplicate = a; - } - }); - - return duplicate; - }; - - const traverse = (item, parent) => { - - const duplicate = findDuplicate(item); - if(duplicate){ - return; - } - - const annotation = loadAnnotationItem(item); - - for(const childItem of item.children){ - traverse(childItem, annotation); - } - - parent.add(annotation); - - }; - - for(const item of data){ - traverse(item, viewer.scene.annotations); - } - - } - - function loadProfile(viewer, data){ - - const {name, points} = data; - - const duplicate = viewer.scene.profiles.find(profile => profile.uuid === data.uuid); - if(duplicate){ - return; - } - - let profile = new Potree.Profile(); - profile.name = name; - profile.uuid = data.uuid; - - profile.setWidth(data.width); - - for(const point of points){ - profile.addMarker(new Vector3(...point)); - } - - viewer.scene.addProfile(profile); - } - - function loadClassification(viewer, data){ - if(!data){ - return; - } - - const classifications = data; - - viewer.setClassifications(classifications); - } - - async function loadProject(viewer, data){ - - if(data.type !== "Potree"){ - console.error("not a valid Potree project"); - return; - } - - loadSettings(viewer, data.settings); - - loadView(viewer, data.view); - - const pointcloudPromises = []; - for(const pointcloud of data.pointclouds){ - const promise = loadPointCloud(viewer, pointcloud); - pointcloudPromises.push(promise); - } - - for(const measure of data.measurements){ - loadMeasurement(viewer, measure); - } - - for(const volume of data.volumes){ - loadVolume(viewer, volume); - } - - for(const animation of data.cameraAnimations){ - loadCameraAnimation(viewer, animation); - } - - for(const profile of data.profiles){ - loadProfile(viewer, profile); - } - - if(data.orientedImages){ - for(const images of data.orientedImages){ - loadOrientedImages(viewer, images); - } - } - - loadAnnotations(viewer, data.annotations); - - loadClassification(viewer, data.classification); - - // need to load at least one point cloud that defines the scene projection, - // before we can load stuff in other projections such as geopackages - //await Promise.any(pointcloudPromises); // (not yet supported) - Utils.waitAny(pointcloudPromises).then( () => { - if(data.geopackages){ - for(const geopackage of data.geopackages){ - loadGeopackage(viewer, geopackage); - } - } - }); - - await Promise.all(pointcloudPromises); + function createVolumeData(volume){ + const data = { + uuid: volume.uuid, + type: volume.constructor.name, + name: volume.name, + position: volume.position.toArray(), + rotation: volume.rotation.toArray(), + scale: volume.scale.toArray(), + visible: volume.visible, + clip: volume.clip, + }; + + return data; + } + + function createCameraAnimationData(animation){ + + const controlPoints = animation.controlPoints.map( cp => { + const cpdata = { + position: cp.position.toArray(), + target: cp.target.toArray(), + }; + + return cpdata; + }); + + const data = { + uuid: animation.uuid, + name: animation.name, + duration: animation.duration, + t: animation.t, + curveType: animation.curveType, + visible: animation.visible, + controlPoints: controlPoints, + }; + + return data; + } + + function createMeasurementData(measurement){ + + const data = { + uuid: measurement.uuid, + name: measurement.name, + points: measurement.points.map(p => p.position.toArray()), + showDistances: measurement.showDistances, + showCoordinates: measurement.showCoordinates, + showArea: measurement.showArea, + closed: measurement.closed, + showAngles: measurement.showAngles, + showHeight: measurement.showHeight, + showCircle: measurement.showCircle, + showAzimuth: measurement.showAzimuth, + showEdges: measurement.showEdges, + color: measurement.color.toArray(), + }; + + return data; + } + + function createOrientedImagesData(images){ + const data = { + cameraParamsPath: images.cameraParamsPath, + imageParamsPath: images.imageParamsPath, + }; + + return data; + } + + function createGeopackageData(geopackage){ + const data = { + path: geopackage.path, + }; + + return data; + } + + function createAnnotationData(annotation){ + + const data = { + uuid: annotation.uuid, + title: annotation.title.toString(), + description: annotation.description, + position: annotation.position.toArray(), + offset: annotation.offset.toArray(), + children: [], + }; + + if(annotation.cameraPosition){ + data.cameraPosition = annotation.cameraPosition.toArray(); + } + + if(annotation.cameraTarget){ + data.cameraTarget = annotation.cameraTarget.toArray(); + } + + if(typeof annotation.radius !== "undefined"){ + data.radius = annotation.radius; + } + + return data; + } + + function createAnnotationsData(viewer){ + + const map = new Map(); + + viewer.scene.annotations.traverseDescendants(a => { + const aData = createAnnotationData(a); + + map.set(a, aData); + }); + + for(const [annotation, data] of map){ + for(const child of annotation.children){ + const childData = map.get(child); + data.children.push(childData); + } + } + + const annotations = viewer.scene.annotations.children.map(a => map.get(a)); + + return annotations; + } + + function createSettingsData(viewer){ + return { + pointBudget: viewer.getPointBudget(), + fov: viewer.getFOV(), + edlEnabled: viewer.getEDLEnabled(), + edlRadius: viewer.getEDLRadius(), + edlStrength: viewer.getEDLStrength(), + background: viewer.getBackground(), + minNodeSize: viewer.getMinNodeSize(), + showBoundingBoxes: viewer.getShowBoundingBox(), + }; + } + + function createSceneContentData(viewer){ + + const data = []; + + const potreeObjects = []; + + viewer.scene.scene.traverse(node => { + if(node.potree){ + potreeObjects.push(node); + } + }); + + for(const object of potreeObjects){ + + if(object.potree.file){ + const saveObject = { + file: object.potree.file, + }; + + data.push(saveObject); + } + + + } + + + return data; + } + + function createViewData(viewer){ + const view = viewer.scene.view; + + const data = { + position: view.position.toArray(), + target: view.getPivot().toArray(), + }; + + return data; + } + + function createClassificationData(viewer){ + const classifications = viewer.classifications; + + const data = classifications; + + return data; + } + + function saveProject(viewer) { + + const scene = viewer.scene; + + const data = { + type: "Potree", + version: 1.7, + settings: createSettingsData(viewer), + view: createViewData(viewer), + classification: createClassificationData(viewer), + pointclouds: scene.pointclouds.map(createPointcloudData), + measurements: scene.measurements.map(createMeasurementData), + volumes: scene.volumes.map(createVolumeData), + cameraAnimations: scene.cameraAnimations.map(createCameraAnimationData), + profiles: scene.profiles.map(createProfileData), + annotations: createAnnotationsData(viewer), + orientedImages: scene.orientedImages.map(createOrientedImagesData), + geopackages: scene.geopackages.map(createGeopackageData), + // objects: createSceneContentData(viewer), + }; + + return data; + } + + class ControlPoint{ + + constructor(){ + this.position = new Vector3(0, 0, 0); + this.target = new Vector3(0, 0, 0); + this.positionHandle = null; + this.targetHandle = null; + } + + }; + + + + class CameraAnimation extends EventDispatcher{ + + constructor(viewer){ + super(); + + this.viewer = viewer; + + this.selectedElement = null; + + this.controlPoints = []; + + this.uuid = MathUtils.generateUUID(); + + this.node = new Object3D(); + this.node.name = "camera animation"; + this.viewer.scene.scene.add(this.node); + + this.frustum = this.createFrustum(); + this.node.add(this.frustum); + + this.name = "Camera Animation"; + this.duration = 5; + this.t = 0; + // "centripetal", "chordal", "catmullrom" + this.curveType = "centripetal"; + this.visible = true; + + this.createUpdateHook(); + this.createPath(); + } + + static defaultFromView(viewer){ + const animation = new CameraAnimation(viewer); + + const camera = viewer.scene.getActiveCamera(); + const target = viewer.scene.view.getPivot(); + + const cpCenter = new Vector3( + 0.3 * camera.position.x + 0.7 * target.x, + 0.3 * camera.position.y + 0.7 * target.y, + 0.3 * camera.position.z + 0.7 * target.z, + ); + + const targetCenter = new Vector3( + 0.05 * camera.position.x + 0.95 * target.x, + 0.05 * camera.position.y + 0.95 * target.y, + 0.05 * camera.position.z + 0.95 * target.z, + ); + + const r = camera.position.distanceTo(target) * 0.3; + + //const dir = target.clone().sub(camera.position).normalize(); + const angle = Utils.computeAzimuth(camera.position, target); + + const n = 5; + for(let i = 0; i < n; i++){ + let u = 1.5 * Math.PI * (i / n) + angle; + + const dx = r * Math.cos(u); + const dy = r * Math.sin(u); + + const cpPos = [ + cpCenter.x + dx, + cpCenter.y + dy, + cpCenter.z, + ]; + + const targetPos = [ + targetCenter.x + dx * 0.1, + targetCenter.y + dy * 0.1, + targetCenter.z, + ]; + + const cp = animation.createControlPoint(); + cp.position.set(...cpPos); + cp.target.set(...targetPos); + } + + return animation; + } + + createUpdateHook(){ + const viewer = this.viewer; + + viewer.addEventListener("update", () => { + + const camera = viewer.scene.getActiveCamera(); + const {width, height} = viewer.renderer.getSize(new Vector2()); + + this.node.visible = this.visible; + + for(const cp of this.controlPoints){ + + { // position + const projected = cp.position.clone().project(camera); + + const visible = this.visible && (projected.z < 1 && projected.z > -1); + + if(visible){ + const x = width * (projected.x * 0.5 + 0.5); + const y = height - height * (projected.y * 0.5 + 0.5); + + cp.positionHandle.svg.style.left = x - cp.positionHandle.svg.clientWidth / 2; + cp.positionHandle.svg.style.top = y - cp.positionHandle.svg.clientHeight / 2; + cp.positionHandle.svg.style.display = ""; + }else { + cp.positionHandle.svg.style.display = "none"; + } + } + + { // target + const projected = cp.target.clone().project(camera); + + const visible = this.visible && (projected.z < 1 && projected.z > -1); + + if(visible){ + const x = width * (projected.x * 0.5 + 0.5); + const y = height - height * (projected.y * 0.5 + 0.5); + + cp.targetHandle.svg.style.left = x - cp.targetHandle.svg.clientWidth / 2; + cp.targetHandle.svg.style.top = y - cp.targetHandle.svg.clientHeight / 2; + cp.targetHandle.svg.style.display = ""; + }else { + cp.targetHandle.svg.style.display = "none"; + } + } + + } + + this.line.material.resolution.set(width, height); + + this.updatePath(); + + { // frustum + const frame = this.at(this.t); + const frustum = this.frustum; + + frustum.position.copy(frame.position); + frustum.lookAt(...frame.target.toArray()); + frustum.scale.set(20, 20, 20); + + frustum.material.resolution.set(width, height); + } + + }); + } + + createControlPoint(index){ + + if(index === undefined){ + index = this.controlPoints.length; + } + + const cp = new ControlPoint(); + + + if(this.controlPoints.length >= 2 && index === 0){ + const cp1 = this.controlPoints[0]; + const cp2 = this.controlPoints[1]; + + const dir = cp1.position.clone().sub(cp2.position).multiplyScalar(0.5); + cp.position.copy(cp1.position).add(dir); + + const tDir = cp1.target.clone().sub(cp2.target).multiplyScalar(0.5); + cp.target.copy(cp1.target).add(tDir); + }else if(this.controlPoints.length >= 2 && index === this.controlPoints.length){ + const cp1 = this.controlPoints[this.controlPoints.length - 2]; + const cp2 = this.controlPoints[this.controlPoints.length - 1]; + + const dir = cp2.position.clone().sub(cp1.position).multiplyScalar(0.5); + cp.position.copy(cp1.position).add(dir); + + const tDir = cp2.target.clone().sub(cp1.target).multiplyScalar(0.5); + cp.target.copy(cp2.target).add(tDir); + }else if(this.controlPoints.length >= 2){ + const cp1 = this.controlPoints[index - 1]; + const cp2 = this.controlPoints[index]; + + cp.position.copy(cp1.position.clone().add(cp2.position).multiplyScalar(0.5)); + cp.target.copy(cp1.target.clone().add(cp2.target).multiplyScalar(0.5)); + } + + // cp.position.copy(viewer.scene.view.position); + // cp.target.copy(viewer.scene.view.getPivot()); + + cp.positionHandle = this.createHandle(cp.position); + cp.targetHandle = this.createHandle(cp.target); + + this.controlPoints.splice(index, 0, cp); + + this.dispatchEvent({ + type: "controlpoint_added", + controlpoint: cp, + }); + + return cp; + } + + removeControlPoint(cp){ + this.controlPoints = this.controlPoints.filter(_cp => _cp !== cp); + + this.dispatchEvent({ + type: "controlpoint_removed", + controlpoint: cp, + }); + + cp.positionHandle.svg.remove(); + cp.targetHandle.svg.remove(); + + // TODO destroy cp + } + + createPath(){ + + { // position + const geometry = new LineGeometry(); + + let material = new LineMaterial({ + color: 0x00ff00, + dashSize: 5, + gapSize: 2, + linewidth: 2, + resolution: new Vector2(1000, 1000), + }); + + const line = new Line2(geometry, material); + + this.line = line; + this.node.add(line); + } + + { // target + const geometry = new LineGeometry(); + + let material = new LineMaterial({ + color: 0x0000ff, + dashSize: 5, + gapSize: 2, + linewidth: 2, + resolution: new Vector2(1000, 1000), + }); + + const line = new Line2(geometry, material); + + this.targetLine = line; + this.node.add(line); + } + } + + createFrustum(){ + + const f = 0.3; + + const positions = [ + 0, 0, 0, + -f, -f, +1, + + 0, 0, 0, + f, -f, +1, + + 0, 0, 0, + f, f, +1, + + 0, 0, 0, + -f, f, +1, + + -f, -f, +1, + f, -f, +1, + + f, -f, +1, + f, f, +1, + + f, f, +1, + -f, f, +1, + + -f, f, +1, + -f, -f, +1, + ]; + + const geometry = new LineGeometry(); + + geometry.setPositions(positions); + geometry.verticesNeedUpdate = true; + geometry.computeBoundingSphere(); + + let material = new LineMaterial({ + color: 0xff0000, + linewidth: 2, + resolution: new Vector2(1000, 1000), + }); + + const line = new Line2(geometry, material); + line.computeLineDistances(); + + return line; + } + + updatePath(){ + + { // positions + const positions = this.controlPoints.map(cp => cp.position); + const first = positions[0]; + + const curve = new CatmullRomCurve3(positions); + curve.curveType = this.curveType; + + const n = 100; + + const curvePositions = []; + for(let k = 0; k <= n; k++){ + const t = k / n; + + const position = curve.getPoint(t).sub(first); + + curvePositions.push(position.x, position.y, position.z); + } + + this.line.geometry.setPositions(curvePositions); + this.line.geometry.verticesNeedUpdate = true; + this.line.geometry.computeBoundingSphere(); + this.line.position.copy(first); + this.line.computeLineDistances(); + + this.cameraCurve = curve; + } + + { // targets + const positions = this.controlPoints.map(cp => cp.target); + const first = positions[0]; + + const curve = new CatmullRomCurve3(positions); + curve.curveType = this.curveType; + + const n = 100; + + const curvePositions = []; + for(let k = 0; k <= n; k++){ + const t = k / n; + + const position = curve.getPoint(t).sub(first); + + curvePositions.push(position.x, position.y, position.z); + } + + this.targetLine.geometry.setPositions(curvePositions); + this.targetLine.geometry.verticesNeedUpdate = true; + this.targetLine.geometry.computeBoundingSphere(); + this.targetLine.position.copy(first); + this.targetLine.computeLineDistances(); + + this.targetCurve = curve; + } + } + + at(t){ + + if(t > 1){ + t = 1; + }else if(t < 0){ + t = 0; + } + + const camPos = this.cameraCurve.getPointAt(t); + const target = this.targetCurve.getPointAt(t); + + const frame = { + position: camPos, + target: target, + }; + + return frame; + } + + set(t){ + this.t = t; + } + + createHandle(vector){ + + const svgns = "http://www.w3.org/2000/svg"; + const svg = document.createElementNS(svgns, "svg"); + + svg.setAttribute("width", "2em"); + svg.setAttribute("height", "2em"); + svg.setAttribute("position", "absolute"); + + svg.style.left = "50px"; + svg.style.top = "50px"; + svg.style.position = "absolute"; + svg.style.zIndex = "10000"; + + const circle = document.createElementNS(svgns, 'circle'); + circle.setAttributeNS(null, 'cx', "1em"); + circle.setAttributeNS(null, 'cy', "1em"); + circle.setAttributeNS(null, 'r', "0.5em"); + circle.setAttributeNS(null, 'style', 'fill: red; stroke: black; stroke-width: 0.2em;' ); + svg.appendChild(circle); + + + const element = this.viewer.renderer.domElement.parentElement; + element.appendChild(svg); + + + const startDrag = (evt) => { + this.selectedElement = svg; + + document.addEventListener("mousemove", drag); + }; + + const endDrag = (evt) => { + this.selectedElement = null; + + document.removeEventListener("mousemove", drag); + }; + + const drag = (evt) => { + if (this.selectedElement) { + evt.preventDefault(); + + const rect = viewer.renderer.domElement.getBoundingClientRect(); + + const x = evt.clientX - rect.x; + const y = evt.clientY - rect.y; + + const {width, height} = this.viewer.renderer.getSize(new Vector2()); + const camera = this.viewer.scene.getActiveCamera(); + //const cp = this.controlPoints.find(cp => cp.handle.svg === svg); + const projected = vector.clone().project(camera); + + projected.x = ((x / width) - 0.5) / 0.5; + projected.y = (-(y - height) / height - 0.5) / 0.5; + + const unprojected = projected.clone().unproject(camera); + vector.set(unprojected.x, unprojected.y, unprojected.z); + + + } + }; + + svg.addEventListener('mousedown', startDrag); + svg.addEventListener('mouseup', endDrag); + + const handle = { + svg: svg, + }; + + return handle; + } + + setVisible(visible){ + this.node.visible = visible; + + const display = visible ? "" : "none"; + + for(const cp of this.controlPoints){ + cp.positionHandle.svg.style.display = display; + cp.targetHandle.svg.style.display = display; + } + + this.visible = visible; + } + + setDuration(duration){ + this.duration = duration; + } + + getDuration(duration){ + return this.duration; + } + + play(){ + + const tStart = performance.now(); + const duration = this.duration; + + const originalyVisible = this.visible; + this.setVisible(false); + + const onUpdate = (delta) => { + + let tNow = performance.now(); + let elapsed = (tNow - tStart) / 1000; + let t = elapsed / duration; + + this.set(t); + + const frame = this.at(t); + + viewer.scene.view.position.copy(frame.position); + viewer.scene.view.lookAt(frame.target); + + + if(t > 1){ + this.setVisible(originalyVisible); + + this.viewer.removeEventListener("update", onUpdate); + } + + }; + + this.viewer.addEventListener("update", onUpdate); + + } + + } + + function loadPointCloud(viewer, data){ + + let loadMaterial = (target) => { + + if(data.material){ + + if(data.material.activeAttributeName != null){ + target.activeAttributeName = data.material.activeAttributeName; + } + + if(data.material.ranges != null){ + for(let range of data.material.ranges){ + + if(range.name === "elevationRange"){ + target.elevationRange = range.value; + }else if(range.name === "intensityRange"){ + target.intensityRange = range.value; + }else { + target.setRange(range.name, range.value); + } + + } + } + + if(data.material.size != null){ + target.size = data.material.size; + } + + if(data.material.minSize != null){ + target.minSize = data.material.minSize; + } + + if(data.material.pointSizeType != null){ + target.pointSizeType = PointSizeType[data.material.pointSizeType]; + } + + if(data.material.matcap != null){ + target.matcap = data.material.matcap; + } + + }else if(data.activeAttributeName != null){ + target.activeAttributeName = data.activeAttributeName; + }else { + // no material data + } + + }; + + const promise = new Promise((resolve) => { + + const names = viewer.scene.pointclouds.map(p => p.name); + const alreadyExists = names.includes(data.name); + + if(alreadyExists){ + resolve(); + return; + } + + Potree.loadPointCloud(data.url, data.name, (e) => { + const {pointcloud} = e; + + pointcloud.position.set(...data.position); + pointcloud.rotation.set(...data.rotation); + pointcloud.scale.set(...data.scale); + + loadMaterial(pointcloud.material); + + viewer.scene.addPointCloud(pointcloud); + + resolve(pointcloud); + }); + }); + + return promise; + } + + function loadMeasurement(viewer, data){ + + const duplicate = viewer.scene.measurements.find(measure => measure.uuid === data.uuid); + if(duplicate){ + return; + } + + const measure = new Measure(); + + measure.uuid = data.uuid; + measure.name = data.name; + measure.showDistances = data.showDistances; + measure.showCoordinates = data.showCoordinates; + measure.showArea = data.showArea; + measure.closed = data.closed; + measure.showAngles = data.showAngles; + measure.showHeight = data.showHeight; + measure.showCircle = data.showCircle; + measure.showAzimuth = data.showAzimuth; + measure.showEdges = data.showEdges; + // color + + for(const point of data.points){ + const pos = new Vector3(...point); + measure.addMarker(pos); + } + + viewer.scene.addMeasurement(measure); + + } + + function loadVolume(viewer, data){ + + const duplicate = viewer.scene.volumes.find(volume => volume.uuid === data.uuid); + if(duplicate){ + return; + } + + let volume = new Potree[data.type]; + + volume.uuid = data.uuid; + volume.name = data.name; + volume.position.set(...data.position); + volume.rotation.set(...data.rotation); + volume.scale.set(...data.scale); + volume.visible = data.visible; + volume.clip = data.clip; + + viewer.scene.addVolume(volume); + } + + function loadCameraAnimation(viewer, data){ + + const duplicate = viewer.scene.cameraAnimations.find(a => a.uuid === data.uuid); + if(duplicate){ + return; + } + + const animation = new CameraAnimation(viewer); + + animation.uuid = data.uuid; + animation.name = data.name; + animation.duration = data.duration; + animation.t = data.t; + animation.curveType = data.curveType; + animation.visible = data.visible; + animation.controlPoints = []; + + for(const cpdata of data.controlPoints){ + const cp = animation.createControlPoint(); + + cp.position.set(...cpdata.position); + cp.target.set(...cpdata.target); + } + + viewer.scene.addCameraAnimation(animation); + } + + function loadOrientedImages(viewer, images){ + + const {cameraParamsPath, imageParamsPath} = images; + + const duplicate = viewer.scene.orientedImages.find(i => i.imageParamsPath === imageParamsPath); + if(duplicate){ + return; + } + + Potree.OrientedImageLoader.load(cameraParamsPath, imageParamsPath, viewer).then( images => { + viewer.scene.addOrientedImages(images); + }); + + } + + function loadGeopackage(viewer, geopackage){ + + const path = geopackage.path; + + const duplicate = viewer.scene.geopackages.find(i => i.path === path); + if(duplicate){ + return; + } + + const projection = viewer.getProjection(); + + proj4.defs("WGS84", "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"); + proj4.defs("pointcloud", projection); + const transform = proj4("WGS84", "pointcloud"); + const params = { + transform: transform, + }; + + Potree.GeoPackageLoader.loadUrl(path, params).then(data => { + viewer.scene.addGeopackage(data); + }); + + + } + + function loadSettings(viewer, data){ + if(!data){ + return; + } + + viewer.setPointBudget(data.pointBudget); + viewer.setFOV(data.fov); + viewer.setEDLEnabled(data.edlEnabled); + viewer.setEDLRadius(data.edlRadius); + viewer.setEDLStrength(data.edlStrength); + viewer.setBackground(data.background); + viewer.setMinNodeSize(data.minNodeSize); + viewer.setShowBoundingBox(data.showBoundingBoxes); + } + + function loadView(viewer, view){ + viewer.scene.view.position.set(...view.position); + viewer.scene.view.lookAt(...view.target); + } + + function loadAnnotationItem(item){ + + const annotation = new Annotation({ + position: item.position, + title: item.title, + cameraPosition: item.cameraPosition, + cameraTarget: item.cameraTarget, + }); + + + annotation.description = item.description; + annotation.uuid = item.uuid; + + if(item.offset){ + annotation.offset.set(...item.offset); + } + + return annotation; + } + + function loadAnnotations(viewer, data){ + + if(!data){ + return; + } + + const findDuplicate = (item) => { + + let duplicate = null; + + viewer.scene.annotations.traverse( a => { + if(a.uuid === item.uuid){ + duplicate = a; + } + }); + + return duplicate; + }; + + const traverse = (item, parent) => { + + const duplicate = findDuplicate(item); + if(duplicate){ + return; + } + + const annotation = loadAnnotationItem(item); + + for(const childItem of item.children){ + traverse(childItem, annotation); + } + + parent.add(annotation); + + }; + + for(const item of data){ + traverse(item, viewer.scene.annotations); + } + + } + + function loadProfile(viewer, data){ + + const {name, points} = data; + + const duplicate = viewer.scene.profiles.find(profile => profile.uuid === data.uuid); + if(duplicate){ + return; + } + + let profile = new Potree.Profile(); + profile.name = name; + profile.uuid = data.uuid; + + profile.setWidth(data.width); + + for(const point of points){ + profile.addMarker(new Vector3(...point)); + } + + viewer.scene.addProfile(profile); + } + + function loadClassification(viewer, data){ + if(!data){ + return; + } + + const classifications = data; + + viewer.setClassifications(classifications); + } + + async function loadProject(viewer, data){ + + if(data.type !== "Potree"){ + console.error("not a valid Potree project"); + return; + } + + loadSettings(viewer, data.settings); + + loadView(viewer, data.view); + + const pointcloudPromises = []; + for(const pointcloud of data.pointclouds){ + const promise = loadPointCloud(viewer, pointcloud); + pointcloudPromises.push(promise); + } + + for(const measure of data.measurements){ + loadMeasurement(viewer, measure); + } + + for(const volume of data.volumes){ + loadVolume(viewer, volume); + } + + for(const animation of data.cameraAnimations){ + loadCameraAnimation(viewer, animation); + } + + for(const profile of data.profiles){ + loadProfile(viewer, profile); + } + + if(data.orientedImages){ + for(const images of data.orientedImages){ + loadOrientedImages(viewer, images); + } + } + + loadAnnotations(viewer, data.annotations); + + loadClassification(viewer, data.classification); + + // need to load at least one point cloud that defines the scene projection, + // before we can load stuff in other projections such as geopackages + //await Promise.any(pointcloudPromises); // (not yet supported) + Utils.waitAny(pointcloudPromises).then( () => { + if(data.geopackages){ + for(const geopackage of data.geopackages){ + loadGeopackage(viewer, geopackage); + } + } + }); + + await Promise.all(pointcloudPromises); } // @@ -66254,104 +66254,104 @@ void main() { } } - class OctreeGeometry{ - - constructor(){ - this.url = null; - this.spacing = 0; - this.boundingBox = null; - this.root = null; - this.pointAttributes = null; - this.loader = null; - } - - }; - - class OctreeGeometryNode{ - - constructor(name, octreeGeometry, boundingBox){ - this.id = OctreeGeometryNode.IDCount++; - this.name = name; - this.index = parseInt(name.charAt(name.length - 1)); - this.octreeGeometry = octreeGeometry; - this.boundingBox = boundingBox; - this.boundingSphere = boundingBox.getBoundingSphere(new Sphere()); - this.children = {}; - this.numPoints = 0; - this.level = null; - this.oneTimeDisposeHandlers = []; - } - - isGeometryNode(){ - return true; - } - - getLevel(){ - return this.level; - } - - isTreeNode(){ - return false; - } - - isLoaded(){ - return this.loaded; - } - - getBoundingSphere(){ - return this.boundingSphere; - } - - getBoundingBox(){ - return this.boundingBox; - } - - getChildren(){ - let children = []; - - for (let i = 0; i < 8; i++) { - if (this.children[i]) { - children.push(this.children[i]); - } - } - - return children; - } - - getBoundingBox(){ - return this.boundingBox; - } - - load(){ - - if (Potree.numNodesLoading >= Potree.maxNodesLoading) { - return; - } - - this.octreeGeometry.loader.load(this); - } - - getNumPoints(){ - return this.numPoints; - } - - dispose(){ - if (this.geometry && this.parent != null) { - this.geometry.dispose(); - this.geometry = null; - this.loaded = false; - - // this.dispatchEvent( { type: 'dispose' } ); - for (let i = 0; i < this.oneTimeDisposeHandlers.length; i++) { - let handler = this.oneTimeDisposeHandlers[i]; - handler(); - } - this.oneTimeDisposeHandlers = []; - } - } - - }; - + class OctreeGeometry{ + + constructor(){ + this.url = null; + this.spacing = 0; + this.boundingBox = null; + this.root = null; + this.pointAttributes = null; + this.loader = null; + } + + }; + + class OctreeGeometryNode{ + + constructor(name, octreeGeometry, boundingBox){ + this.id = OctreeGeometryNode.IDCount++; + this.name = name; + this.index = parseInt(name.charAt(name.length - 1)); + this.octreeGeometry = octreeGeometry; + this.boundingBox = boundingBox; + this.boundingSphere = boundingBox.getBoundingSphere(new Sphere()); + this.children = {}; + this.numPoints = 0; + this.level = null; + this.oneTimeDisposeHandlers = []; + } + + isGeometryNode(){ + return true; + } + + getLevel(){ + return this.level; + } + + isTreeNode(){ + return false; + } + + isLoaded(){ + return this.loaded; + } + + getBoundingSphere(){ + return this.boundingSphere; + } + + getBoundingBox(){ + return this.boundingBox; + } + + getChildren(){ + let children = []; + + for (let i = 0; i < 8; i++) { + if (this.children[i]) { + children.push(this.children[i]); + } + } + + return children; + } + + getBoundingBox(){ + return this.boundingBox; + } + + load(){ + + if (Potree.numNodesLoading >= Potree.maxNodesLoading) { + return; + } + + this.octreeGeometry.loader.load(this); + } + + getNumPoints(){ + return this.numPoints; + } + + dispose(){ + if (this.geometry && this.parent != null) { + this.geometry.dispose(); + this.geometry = null; + this.loaded = false; + + // this.dispatchEvent( { type: 'dispose' } ); + for (let i = 0; i < this.oneTimeDisposeHandlers.length; i++) { + let handler = this.oneTimeDisposeHandlers[i]; + handler(); + } + this.oneTimeDisposeHandlers = []; + } + } + + }; + OctreeGeometryNode.IDCount = 0; // let loadedNodes = new Set(); @@ -70218,58 +70218,58 @@ void main() { } - class Compass{ - - constructor(viewer){ - this.viewer = viewer; - - this.visible = false; - this.dom = this.createElement(); - - viewer.addEventListener("update", () => { - const direction = viewer.scene.view.direction.clone(); - direction.z = 0; - direction.normalize(); - - const camera = viewer.scene.getActiveCamera(); - - const p1 = camera.getWorldPosition(new Vector3()); - const p2 = p1.clone().add(direction); - - const projection = viewer.getProjection(); - const azimuth = Utils.computeAzimuth(p1, p2, projection); - - this.dom.css("transform", `rotateZ(${-azimuth}rad)`); - }); - - this.dom.click( () => { - viewer.setTopView(); - }); - - const renderArea = $(viewer.renderArea); - renderArea.append(this.dom); - - this.setVisible(this.visible); - } - - setVisible(visible){ - this.visible = visible; - - const value = visible ? "" : "none"; - this.dom.css("display", value); - } - - isVisible(){ - return this.visible; - } - - createElement(){ - const style = `style="position: absolute; top: 10px; right: 10px; z-index: 10000; width: 64px;"`; - const img = $(``); - - return img; - } - + class Compass{ + + constructor(viewer){ + this.viewer = viewer; + + this.visible = false; + this.dom = this.createElement(); + + viewer.addEventListener("update", () => { + const direction = viewer.scene.view.direction.clone(); + direction.z = 0; + direction.normalize(); + + const camera = viewer.scene.getActiveCamera(); + + const p1 = camera.getWorldPosition(new Vector3()); + const p2 = p1.clone().add(direction); + + const projection = viewer.getProjection(); + const azimuth = Utils.computeAzimuth(p1, p2, projection); + + this.dom.css("transform", `rotateZ(${-azimuth}rad)`); + }); + + this.dom.click( () => { + viewer.setTopView(); + }); + + const renderArea = $(viewer.renderArea); + renderArea.append(this.dom); + + this.setVisible(this.visible); + } + + setVisible(visible){ + this.visible = visible; + + const value = visible ? "" : "none"; + this.dom.css("display", value); + } + + isVisible(){ + return this.visible; + } + + createElement(){ + const style = `style="position: absolute; top: 10px; right: 10px; z-index: 10000; width: 64px;"`; + const img = $(``); + + return img; + } + }; class PotreeRenderer { @@ -76444,168 +76444,168 @@ ENDSEC } - function addCommas(nStr){ - nStr += ''; - let x = nStr.split('.'); - let x1 = x[0]; - let x2 = x.length > 1 ? '.' + x[1] : ''; - let rgx = /(\d+)(\d{3})/; - while (rgx.test(x1)) { - x1 = x1.replace(rgx, '$1' + ',' + '$2'); - } - return x1 + x2; - }; - - function format(value){ - return addCommas(value.toFixed(3)); - }; - - class HierarchicalSlider{ - - constructor(params = {}){ - - this.element = document.createElement("div"); - - this.labels = []; - this.sliders = []; - this.range = params.range != null ? params.range : [0, 1]; - this.slide = params.slide != null ? params.slide : null; - this.step = params.step != null ? params.step : 0.0001; - - let levels = params.levels != null ? params.levels : 1; - - for(let level = 0; level < levels; level++){ - this.addLevel(); - } - - } - - setRange(range){ - this.range = [...range]; - - { // root slider - let slider = this.sliders[0]; - - $(slider).slider({ - min: range[0], - max: range[1], - }); - } - - for(let i = 1; i < this.sliders.length; i++){ - let parentSlider = this.sliders[i - 1]; - let slider = this.sliders[i]; - - let parentValues = $(parentSlider).slider("option", "values"); - let childRange = [...parentValues]; - - $(slider).slider({ - min: childRange[0], - max: childRange[1], - }); - } - - this.updateLabels(); - } - - setValues(values){ - for(let slider of this.sliders){ - $(slider).slider({ - values: [...values], - }); - } - - this.updateLabels(); - } - - addLevel(){ - const elLevel = document.createElement("li"); - const elRange = document.createTextNode("Range: "); - const label = document.createElement("span"); - const slider = document.createElement("div"); - - let level = this.sliders.length; - let [min, max] = [0, 0]; - - if(this.sliders.length === 0){ - [min, max] = this.range; - }else { - let parentSlider = this.sliders[this.sliders.length - 1]; - [min, max] = $(parentSlider).slider("option", "values"); - } - - $(slider).slider({ - range: true, - min: min, - max: max, - step: this.step, - values: [min, max], - slide: (event, ui) => { - - // set all descendants to same range - let levels = this.sliders.length; - for(let i = level + 1; i < levels; i++){ - let descendant = this.sliders[i]; - - $(descendant).slider({ - range: true, - min: ui.values[0], - max: ui.values[1], - values: [...ui.values], - }); - } - - if(this.slide){ - let values = [...ui.values]; - - this.slide({ - target: this, - range: this.range, - values: values, - }); - } - - this.updateLabels(); - }, - }); - - elLevel.append(elRange, label, slider); - - this.sliders.push(slider); - this.labels.push(label); - this.element.append(elLevel); - - this.updateLabels(); - } - - removeLevel(){ - - } - - updateSliders(){ - - } - - updateLabels(){ - - let levels = this.sliders.length; - - for(let i = 0; i < levels; i++){ - - let slider = this.sliders[i]; - let label = this.labels[i]; - - let [min, max] = $(slider).slider("option", "values"); - let strMin = format(min); - let strMax = format(max); - let strLabel = `${strMin} to ${strMax}`; - - label.innerHTML = strLabel; - } - - } - - + function addCommas(nStr){ + nStr += ''; + let x = nStr.split('.'); + let x1 = x[0]; + let x2 = x.length > 1 ? '.' + x[1] : ''; + let rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + ',' + '$2'); + } + return x1 + x2; + }; + + function format(value){ + return addCommas(value.toFixed(3)); + }; + + class HierarchicalSlider{ + + constructor(params = {}){ + + this.element = document.createElement("div"); + + this.labels = []; + this.sliders = []; + this.range = params.range != null ? params.range : [0, 1]; + this.slide = params.slide != null ? params.slide : null; + this.step = params.step != null ? params.step : 0.0001; + + let levels = params.levels != null ? params.levels : 1; + + for(let level = 0; level < levels; level++){ + this.addLevel(); + } + + } + + setRange(range){ + this.range = [...range]; + + { // root slider + let slider = this.sliders[0]; + + $(slider).slider({ + min: range[0], + max: range[1], + }); + } + + for(let i = 1; i < this.sliders.length; i++){ + let parentSlider = this.sliders[i - 1]; + let slider = this.sliders[i]; + + let parentValues = $(parentSlider).slider("option", "values"); + let childRange = [...parentValues]; + + $(slider).slider({ + min: childRange[0], + max: childRange[1], + }); + } + + this.updateLabels(); + } + + setValues(values){ + for(let slider of this.sliders){ + $(slider).slider({ + values: [...values], + }); + } + + this.updateLabels(); + } + + addLevel(){ + const elLevel = document.createElement("li"); + const elRange = document.createTextNode("Range: "); + const label = document.createElement("span"); + const slider = document.createElement("div"); + + let level = this.sliders.length; + let [min, max] = [0, 0]; + + if(this.sliders.length === 0){ + [min, max] = this.range; + }else { + let parentSlider = this.sliders[this.sliders.length - 1]; + [min, max] = $(parentSlider).slider("option", "values"); + } + + $(slider).slider({ + range: true, + min: min, + max: max, + step: this.step, + values: [min, max], + slide: (event, ui) => { + + // set all descendants to same range + let levels = this.sliders.length; + for(let i = level + 1; i < levels; i++){ + let descendant = this.sliders[i]; + + $(descendant).slider({ + range: true, + min: ui.values[0], + max: ui.values[1], + values: [...ui.values], + }); + } + + if(this.slide){ + let values = [...ui.values]; + + this.slide({ + target: this, + range: this.range, + values: values, + }); + } + + this.updateLabels(); + }, + }); + + elLevel.append(elRange, label, slider); + + this.sliders.push(slider); + this.labels.push(label); + this.element.append(elLevel); + + this.updateLabels(); + } + + removeLevel(){ + + } + + updateSliders(){ + + } + + updateLabels(){ + + let levels = this.sliders.length; + + for(let i = 0; i < levels; i++){ + + let slider = this.sliders[i]; + let label = this.labels[i]; + + let [min, max] = $(slider).slider("option", "values"); + let strMin = format(min); + let strMax = format(max); + let strLabel = `${strMin} to ${strMax}`; + + label.innerHTML = strLabel; + } + + } + + } class OrientedImageControls extends EventDispatcher{ @@ -76813,827 +76813,827 @@ ENDSEC } }; - // https://support.pix4d.com/hc/en-us/articles/205675256-How-are-yaw-pitch-roll-defined - // https://support.pix4d.com/hc/en-us/articles/202558969-How-are-omega-phi-kappa-defined - - function createMaterial(){ - - let vertexShader = ` - uniform float uNear; - varying vec2 vUV; - varying vec4 vDebug; - - void main(){ - vDebug = vec4(0.0, 1.0, 0.0, 1.0); - vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0); - // make sure that this mesh is at least in front of the near plane - modelViewPosition.xyz += normalize(modelViewPosition.xyz) * uNear; - gl_Position = projectionMatrix * modelViewPosition; - vUV = uv; - } - `; - - let fragmentShader = ` - uniform sampler2D tColor; - uniform float uOpacity; - varying vec2 vUV; - varying vec4 vDebug; - void main(){ - vec4 color = texture2D(tColor, vUV); - gl_FragColor = color; - gl_FragColor.a = uOpacity; - } - `; - const material = new ShaderMaterial( { - uniforms: { - // time: { value: 1.0 }, - // resolution: { value: new THREE.Vector2() } - tColor: {value: new Texture() }, - uNear: {value: 0.0}, - uOpacity: {value: 1.0}, - }, - vertexShader: vertexShader, - fragmentShader: fragmentShader, - side: DoubleSide, - } ); - - material.side = DoubleSide; - - return material; - } - - const planeGeometry = new PlaneGeometry(1, 1); - const lineGeometry = new Geometry(); - - lineGeometry.vertices.push( - new Vector3(-0.5, -0.5, 0), - new Vector3( 0.5, -0.5, 0), - new Vector3( 0.5, 0.5, 0), - new Vector3(-0.5, 0.5, 0), - new Vector3(-0.5, -0.5, 0), - ); - - class OrientedImage{ - - constructor(id){ - - this.id = id; - this.fov = 1.0; - this.position = new Vector3(); - this.rotation = new Vector3(); - this.width = 0; - this.height = 0; - this.fov = 1.0; - - const material = createMaterial(); - const lineMaterial = new LineBasicMaterial( { color: 0x00ff00 } ); - this.mesh = new Mesh(planeGeometry, material); - this.line = new Line(lineGeometry, lineMaterial); - this.texture = null; - - this.mesh.orientedImage = this; - } - - set(position, rotation, dimension, fov){ - - let radians = rotation.map(MathUtils.degToRad); - - this.position.set(...position); - this.mesh.position.set(...position); - - this.rotation.set(...radians); - this.mesh.rotation.set(...radians); - - [this.width, this.height] = dimension; - this.mesh.scale.set(this.width / this.height, 1, 1); - - this.fov = fov; - - this.updateTransform(); - } - - updateTransform(){ - let {mesh, line, fov} = this; - - mesh.updateMatrixWorld(); - const dir = mesh.getWorldDirection(); - const alpha = MathUtils.degToRad(fov / 2); - const d = -0.5 / Math.tan(alpha); - const move = dir.clone().multiplyScalar(d); - mesh.position.add(move); - - line.position.copy(mesh.position); - line.scale.copy(mesh.scale); - line.rotation.copy(mesh.rotation); - } - - }; - - class OrientedImages extends EventDispatcher{ - - constructor(){ - super(); - - this.node = null; - this.cameraParams = null; - this.imageParams = null; - this.images = null; - this._visible = true; - } - - set visible(visible){ - if(this._visible === visible){ - return; - } - - for(const image of this.images){ - image.mesh.visible = visible; - image.line.visible = visible; - } - - this._visible = visible; - this.dispatchEvent({ - type: "visibility_changed", - images: this, - }); - } - - get visible(){ - return this._visible; - } - - - }; - - class OrientedImageLoader{ - - static async loadCameraParams(path){ - const res = await fetch(path); - const text = await res.text(); - - const parser = new DOMParser(); - const doc = parser.parseFromString(text, "application/xml"); - - const width = parseInt(doc.getElementsByTagName("width")[0].textContent); - const height = parseInt(doc.getElementsByTagName("height")[0].textContent); - const f = parseFloat(doc.getElementsByTagName("f")[0].textContent); - - let a = (height / 2) / f; - let fov = 2 * MathUtils.radToDeg(Math.atan(a)); - - const params = { - path: path, - width: width, - height: height, - f: f, - fov: fov, - }; - - return params; - } - - static async loadImageParams(path){ - - const response = await fetch(path); - if(!response.ok){ - console.error(`failed to load ${path}`); - return; - } - - const content = await response.text(); - const lines = content.split(/\r?\n/); - const imageParams = []; - - for(let i = 1; i < lines.length; i++){ - const line = lines[i]; - - if(line.startsWith("#")){ - continue; - } - - const tokens = line.split(/\s+/); - - if(tokens.length < 6){ - continue; - } - - const params = { - id: tokens[0], - x: Number.parseFloat(tokens[1]), - y: Number.parseFloat(tokens[2]), - z: Number.parseFloat(tokens[3]), - omega: Number.parseFloat(tokens[4]), - phi: Number.parseFloat(tokens[5]), - kappa: Number.parseFloat(tokens[6]), - }; - - // const whitelist = ["47518.jpg"]; - // if(whitelist.includes(params.id)){ - // imageParams.push(params); - // } - imageParams.push(params); - } - - // debug - //return [imageParams[50]]; - - return imageParams; - } - - static async load(cameraParamsPath, imageParamsPath, viewer){ - - const tStart = performance.now(); - - const [cameraParams, imageParams] = await Promise.all([ - OrientedImageLoader.loadCameraParams(cameraParamsPath), - OrientedImageLoader.loadImageParams(imageParamsPath), - ]); - - const orientedImageControls = new OrientedImageControls(viewer); - const raycaster = new Raycaster(); - - const tEnd = performance.now(); - console.log(tEnd - tStart); - - // const sp = new THREE.PlaneGeometry(1, 1); - // const lg = new THREE.Geometry(); - - // lg.vertices.push( - // new THREE.Vector3(-0.5, -0.5, 0), - // new THREE.Vector3( 0.5, -0.5, 0), - // new THREE.Vector3( 0.5, 0.5, 0), - // new THREE.Vector3(-0.5, 0.5, 0), - // new THREE.Vector3(-0.5, -0.5, 0), - // ); - - const {width, height} = cameraParams; - const orientedImages = []; - const sceneNode = new Object3D(); - sceneNode.name = "oriented_images"; - - for(const params of imageParams){ - - // const material = createMaterial(); - // const lm = new THREE.LineBasicMaterial( { color: 0x00ff00 } ); - // const mesh = new THREE.Mesh(sp, material); - - const {x, y, z, omega, phi, kappa} = params; - // const [rx, ry, rz] = [omega, phi, kappa] - // .map(THREE.Math.degToRad); - - // mesh.position.set(x, y, z); - // mesh.scale.set(width / height, 1, 1); - // mesh.rotation.set(rx, ry, rz); - // { - // mesh.updateMatrixWorld(); - // const dir = mesh.getWorldDirection(); - // const alpha = THREE.Math.degToRad(cameraParams.fov / 2); - // const d = -0.5 / Math.tan(alpha); - // const move = dir.clone().multiplyScalar(d); - // mesh.position.add(move); - // } - // sceneNode.add(mesh); - - // const line = new THREE.Line(lg, lm); - // line.position.copy(mesh.position); - // line.scale.copy(mesh.scale); - // line.rotation.copy(mesh.rotation); - // sceneNode.add(line); - - let orientedImage = new OrientedImage(params.id); - // orientedImage.setPosition(x, y, z); - // orientedImage.setRotation(omega, phi, kappa); - // orientedImage.setDimension(width, height); - let position = [x, y, z]; - let rotation = [omega, phi, kappa]; - let dimension = [width, height]; - orientedImage.set(position, rotation, dimension, cameraParams.fov); - - sceneNode.add(orientedImage.mesh); - sceneNode.add(orientedImage.line); - - orientedImages.push(orientedImage); - } - - let hoveredElement = null; - let clipVolume = null; - - const onMouseMove = (evt) => { - const tStart = performance.now(); - if(hoveredElement){ - hoveredElement.line.material.color.setRGB(0, 1, 0); - } - evt.preventDefault(); - - //var array = getMousePosition( container, evt.clientX, evt.clientY ); - const rect = viewer.renderer.domElement.getBoundingClientRect(); - const [x, y] = [evt.clientX, evt.clientY]; - const array = [ - ( x - rect.left ) / rect.width, - ( y - rect.top ) / rect.height - ]; - const onClickPosition = new Vector2(...array); - //const intersects = getIntersects(onClickPosition, scene.children); - const camera = viewer.scene.getActiveCamera(); - const mouse = new Vector3( - + ( onClickPosition.x * 2 ) - 1, - - ( onClickPosition.y * 2 ) + 1 ); - const objects = orientedImages.map(i => i.mesh); - raycaster.setFromCamera( mouse, camera ); - const intersects = raycaster.intersectObjects( objects ); - let selectionChanged = false; - - if ( intersects.length > 0){ - //console.log(intersects); - const intersection = intersects[0]; - const orientedImage = intersection.object.orientedImage; - orientedImage.line.material.color.setRGB(1, 0, 0); - selectionChanged = hoveredElement !== orientedImage; - hoveredElement = orientedImage; - }else { - hoveredElement = null; - } - - let shouldRemoveClipVolume = clipVolume !== null && hoveredElement === null; - let shouldAddClipVolume = clipVolume === null && hoveredElement !== null; - - if(clipVolume !== null && (hoveredElement === null || selectionChanged)){ - // remove existing - viewer.scene.removePolygonClipVolume(clipVolume); - clipVolume = null; - } - - if(shouldAddClipVolume || selectionChanged){ - const img = hoveredElement; - const fov = cameraParams.fov; - const aspect = cameraParams.width / cameraParams.height; - const near = 1.0; - const far = 1000 * 1000; - const camera = new PerspectiveCamera(fov, aspect, near, far); - camera.rotation.order = viewer.scene.getActiveCamera().rotation.order; - camera.rotation.copy(img.mesh.rotation); - { - const mesh = img.mesh; - const dir = mesh.getWorldDirection(); - const pos = mesh.position; - const alpha = MathUtils.degToRad(fov / 2); - const d = 0.5 / Math.tan(alpha); - const newCamPos = pos.clone().add(dir.clone().multiplyScalar(d)); - const newCamDir = pos.clone().sub(newCamPos); - const newCamTarget = new Vector3().addVectors( - newCamPos, - newCamDir.clone().multiplyScalar(viewer.getMoveSpeed())); - camera.position.copy(newCamPos); - } - let volume = new Potree.PolygonClipVolume(camera); - let m0 = new Mesh(); - let m1 = new Mesh(); - let m2 = new Mesh(); - let m3 = new Mesh(); - m0.position.set(-1, -1, 0); - m1.position.set( 1, -1, 0); - m2.position.set( 1, 1, 0); - m3.position.set(-1, 1, 0); - volume.markers.push(m0, m1, m2, m3); - volume.initialized = true; - - viewer.scene.addPolygonClipVolume(volume); - clipVolume = volume; - } - const tEnd = performance.now(); - //console.log(tEnd - tStart); - }; - - const moveToImage = (image) => { - console.log("move to image " + image.id); - - const mesh = image.mesh; - const newCamPos = image.position.clone(); - const newCamTarget = mesh.position.clone(); - - viewer.scene.view.setView(newCamPos, newCamTarget, 500, () => { - orientedImageControls.capture(image); - }); - - if(image.texture === null){ - - const target = image; - - const tmpImagePath = `${Potree.resourcePath}/images/loading.jpg`; - new TextureLoader().load(tmpImagePath, - (texture) => { - if(target.texture === null){ - target.texture = texture; - target.mesh.material.uniforms.tColor.value = texture; - mesh.material.needsUpdate = true; - } - } - ); - - const imagePath = `${imageParamsPath}/../${target.id}`; - new TextureLoader().load(imagePath, - (texture) => { - target.texture = texture; - target.mesh.material.uniforms.tColor.value = texture; - mesh.material.needsUpdate = true; - } - ); - - - } - }; - - const onMouseClick = (evt) => { - - if(orientedImageControls.hasSomethingCaptured()){ - return; - } - - if(hoveredElement){ - moveToImage(hoveredElement); - } - }; - viewer.renderer.domElement.addEventListener( 'mousemove', onMouseMove, false ); - viewer.renderer.domElement.addEventListener( 'mousedown', onMouseClick, false ); - - viewer.addEventListener("update", () => { - - for(const image of orientedImages){ - const world = image.mesh.matrixWorld; - const {width, height} = image; - const aspect = width / height; - - const camera = viewer.scene.getActiveCamera(); - - const imgPos = image.mesh.getWorldPosition(new Vector3()); - const camPos = camera.position; - const d = camPos.distanceTo(imgPos); - - const minSize = 1; // in degrees of fov - const a = MathUtils.degToRad(minSize); - let r = d * Math.tan(a); - r = Math.max(r, 1); - - - image.mesh.scale.set(r * aspect, r, 1); - image.line.scale.set(r * aspect, r, 1); - - image.mesh.material.uniforms.uNear.value = camera.near; - - } - - }); - - const images = new OrientedImages(); - images.node = sceneNode; - images.cameraParamsPath = cameraParamsPath; - images.imageParamsPath = imageParamsPath; - images.cameraParams = cameraParams; - images.imageParams = imageParams; - images.images = orientedImages; - - Potree.debug.moveToImage = moveToImage; - - return images; - } + // https://support.pix4d.com/hc/en-us/articles/205675256-How-are-yaw-pitch-roll-defined + // https://support.pix4d.com/hc/en-us/articles/202558969-How-are-omega-phi-kappa-defined + + function createMaterial(){ + + let vertexShader = ` + uniform float uNear; + varying vec2 vUV; + varying vec4 vDebug; + + void main(){ + vDebug = vec4(0.0, 1.0, 0.0, 1.0); + vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0); + // make sure that this mesh is at least in front of the near plane + modelViewPosition.xyz += normalize(modelViewPosition.xyz) * uNear; + gl_Position = projectionMatrix * modelViewPosition; + vUV = uv; + } + `; + + let fragmentShader = ` + uniform sampler2D tColor; + uniform float uOpacity; + varying vec2 vUV; + varying vec4 vDebug; + void main(){ + vec4 color = texture2D(tColor, vUV); + gl_FragColor = color; + gl_FragColor.a = uOpacity; + } + `; + const material = new ShaderMaterial( { + uniforms: { + // time: { value: 1.0 }, + // resolution: { value: new THREE.Vector2() } + tColor: {value: new Texture() }, + uNear: {value: 0.0}, + uOpacity: {value: 1.0}, + }, + vertexShader: vertexShader, + fragmentShader: fragmentShader, + side: DoubleSide, + } ); + + material.side = DoubleSide; + + return material; } - let sg = new SphereGeometry(1, 8, 8); - let sgHigh = new SphereGeometry(1, 128, 128); - - let sm = new MeshBasicMaterial({side: BackSide}); - let smHovered = new MeshBasicMaterial({side: BackSide, color: 0xff0000}); - - let raycaster = new Raycaster(); - let currentlyHovered = null; - - let previousView = { - controls: null, - position: null, - target: null, - }; - - class Image360{ - - constructor(file, time, longitude, latitude, altitude, course, pitch, roll){ - this.file = file; - this.time = time; - this.longitude = longitude; - this.latitude = latitude; - this.altitude = altitude; - this.course = course; - this.pitch = pitch; - this.roll = roll; - this.mesh = null; - } - }; - - class Images360 extends EventDispatcher{ - - constructor(viewer){ - super(); - - this.viewer = viewer; - - this.selectingEnabled = true; - - this.images = []; - this.node = new Object3D(); - - this.sphere = new Mesh(sgHigh, sm); - this.sphere.visible = false; - this.sphere.scale.set(1000, 1000, 1000); - this.node.add(this.sphere); - this._visible = true; - // this.node.add(label); - - this.focusedImage = null; - - let elUnfocus = document.createElement("input"); - elUnfocus.type = "button"; - elUnfocus.value = "unfocus"; - elUnfocus.style.position = "absolute"; - elUnfocus.style.right = "10px"; - elUnfocus.style.bottom = "10px"; - elUnfocus.style.zIndex = "10000"; - elUnfocus.style.fontSize = "2em"; - elUnfocus.addEventListener("click", () => this.unfocus()); - this.elUnfocus = elUnfocus; - - this.domRoot = viewer.renderer.domElement.parentElement; - this.domRoot.appendChild(elUnfocus); - this.elUnfocus.style.display = "none"; - - viewer.addEventListener("update", () => { - this.update(viewer); - }); - viewer.inputHandler.addInputListener(this); - - this.addEventListener("mousedown", () => { - if(currentlyHovered){ - this.focus(currentlyHovered.image360); - } - }); - - }; - - set visible(visible){ - if(this._visible === visible){ - return; - } - - - for(const image of this.images){ - image.mesh.visible = visible && (this.focusedImage == null); - } - - this.sphere.visible = visible && (this.focusedImage != null); - this._visible = visible; - this.dispatchEvent({ - type: "visibility_changed", - images: this, - }); - } - - get visible(){ - return this._visible; - } - - focus(image360){ - if(this.focusedImage !== null){ - this.unfocus(); - } - - previousView = { - controls: this.viewer.controls, - position: this.viewer.scene.view.position.clone(), - target: viewer.scene.view.getPivot(), - }; - - this.viewer.setControls(this.viewer.orbitControls); - this.viewer.orbitControls.doubleClockZoomEnabled = false; - - for(let image of this.images){ - image.mesh.visible = false; - } - - this.selectingEnabled = false; - - this.sphere.visible = false; - - this.load(image360).then( () => { - this.sphere.visible = true; - this.sphere.material.map = image360.texture; - this.sphere.material.needsUpdate = true; - }); - - { // orientation - let {course, pitch, roll} = image360; - this.sphere.rotation.set( - MathUtils.degToRad(+roll + 90), - MathUtils.degToRad(-pitch), - MathUtils.degToRad(-course + 90), - "ZYX" - ); - } - - this.sphere.position.set(...image360.position); - - let target = new Vector3(...image360.position); - let dir = target.clone().sub(viewer.scene.view.position).normalize(); - let move = dir.multiplyScalar(0.000001); - let newCamPos = target.clone().sub(move); - - viewer.scene.view.setView( - newCamPos, - target, - 500 - ); - - this.focusedImage = image360; - - this.elUnfocus.style.display = ""; - } - - unfocus(){ - this.selectingEnabled = true; - - for(let image of this.images){ - image.mesh.visible = true; - } - - let image = this.focusedImage; - - if(image === null){ - return; - } - - - this.sphere.material.map = null; - this.sphere.material.needsUpdate = true; - this.sphere.visible = false; - - let pos = viewer.scene.view.position; - let target = viewer.scene.view.getPivot(); - let dir = target.clone().sub(pos).normalize(); - let move = dir.multiplyScalar(10); - let newCamPos = target.clone().sub(move); - - viewer.orbitControls.doubleClockZoomEnabled = true; - viewer.setControls(previousView.controls); - - viewer.scene.view.setView( - previousView.position, - previousView.target, - 500 - ); - - - this.focusedImage = null; - - this.elUnfocus.style.display = "none"; - } - - load(image360){ - - return new Promise(resolve => { - let texture = new TextureLoader().load(image360.file, resolve); - texture.wrapS = RepeatWrapping; - texture.repeat.x = -1; - - image360.texture = texture; - }); - - } - - handleHovering(){ - let mouse = viewer.inputHandler.mouse; - let camera = viewer.scene.getActiveCamera(); - let domElement = viewer.renderer.domElement; - - let ray = Potree.Utils.mouseToRay(mouse, camera, domElement.clientWidth, domElement.clientHeight); - - // let tStart = performance.now(); - raycaster.ray.copy(ray); - let intersections = raycaster.intersectObjects(this.node.children); - - if(intersections.length === 0){ - // label.visible = false; - - return; - } - - let intersection = intersections[0]; - currentlyHovered = intersection.object; - currentlyHovered.material = smHovered; - - //label.visible = true; - //label.setText(currentlyHovered.image360.file); - //currentlyHovered.getWorldPosition(label.position); - } - - update(){ - - let {viewer} = this; - - if(currentlyHovered){ - currentlyHovered.material = sm; - currentlyHovered = null; - } - - if(this.selectingEnabled){ - this.handleHovering(); - } - - } - - }; - - - class Images360Loader{ - - static async load(url, viewer, params = {}){ - - if(!params.transform){ - params.transform = { - forward: a => a, - }; - } - - let response = await fetch(`${url}/coordinates.txt`); - let text = await response.text(); - - let lines = text.split(/\r?\n/); - let coordinateLines = lines.slice(1); - - let images360 = new Images360(viewer); - - for(let line of coordinateLines){ - - if(line.trim().length === 0){ - continue; - } - - let tokens = line.split(/\t/); - - let [filename, time, long, lat, alt, course, pitch, roll] = tokens; - time = parseFloat(time); - long = parseFloat(long); - lat = parseFloat(lat); - alt = parseFloat(alt); - course = parseFloat(course); - pitch = parseFloat(pitch); - roll = parseFloat(roll); - - filename = filename.replace(/"/g, ""); - let file = `${url}/${filename}`; - - let image360 = new Image360(file, time, long, lat, alt, course, pitch, roll); - - let xy = params.transform.forward([long, lat]); - let position = [...xy, alt]; - image360.position = position; - - images360.images.push(image360); - } - - Images360Loader.createSceneNodes(images360, params.transform); - - return images360; - - } - - static createSceneNodes(images360, transform){ - - for(let image360 of images360.images){ - let {longitude, latitude, altitude} = image360; - let xy = transform.forward([longitude, latitude]); - - let mesh = new Mesh(sg, sm); - mesh.position.set(...xy, altitude); - mesh.scale.set(1, 1, 1); - mesh.material.transparent = true; - mesh.material.opacity = 0.75; - mesh.image360 = image360; - - { // orientation - var {course, pitch, roll} = image360; - mesh.rotation.set( - MathUtils.degToRad(+roll + 90), - MathUtils.degToRad(-pitch), - MathUtils.degToRad(-course + 90), - "ZYX" - ); - } - - images360.node.add(mesh); - - image360.mesh = mesh; - } - } - - - + const planeGeometry = new PlaneGeometry(1, 1); + const lineGeometry = new Geometry(); + + lineGeometry.vertices.push( + new Vector3(-0.5, -0.5, 0), + new Vector3( 0.5, -0.5, 0), + new Vector3( 0.5, 0.5, 0), + new Vector3(-0.5, 0.5, 0), + new Vector3(-0.5, -0.5, 0), + ); + + class OrientedImage{ + + constructor(id){ + + this.id = id; + this.fov = 1.0; + this.position = new Vector3(); + this.rotation = new Vector3(); + this.width = 0; + this.height = 0; + this.fov = 1.0; + + const material = createMaterial(); + const lineMaterial = new LineBasicMaterial( { color: 0x00ff00 } ); + this.mesh = new Mesh(planeGeometry, material); + this.line = new Line(lineGeometry, lineMaterial); + this.texture = null; + + this.mesh.orientedImage = this; + } + + set(position, rotation, dimension, fov){ + + let radians = rotation.map(MathUtils.degToRad); + + this.position.set(...position); + this.mesh.position.set(...position); + + this.rotation.set(...radians); + this.mesh.rotation.set(...radians); + + [this.width, this.height] = dimension; + this.mesh.scale.set(this.width / this.height, 1, 1); + + this.fov = fov; + + this.updateTransform(); + } + + updateTransform(){ + let {mesh, line, fov} = this; + + mesh.updateMatrixWorld(); + const dir = mesh.getWorldDirection(); + const alpha = MathUtils.degToRad(fov / 2); + const d = -0.5 / Math.tan(alpha); + const move = dir.clone().multiplyScalar(d); + mesh.position.add(move); + + line.position.copy(mesh.position); + line.scale.copy(mesh.scale); + line.rotation.copy(mesh.rotation); + } + + }; + + class OrientedImages extends EventDispatcher{ + + constructor(){ + super(); + + this.node = null; + this.cameraParams = null; + this.imageParams = null; + this.images = null; + this._visible = true; + } + + set visible(visible){ + if(this._visible === visible){ + return; + } + + for(const image of this.images){ + image.mesh.visible = visible; + image.line.visible = visible; + } + + this._visible = visible; + this.dispatchEvent({ + type: "visibility_changed", + images: this, + }); + } + + get visible(){ + return this._visible; + } + + + }; + + class OrientedImageLoader{ + + static async loadCameraParams(path){ + const res = await fetch(path); + const text = await res.text(); + + const parser = new DOMParser(); + const doc = parser.parseFromString(text, "application/xml"); + + const width = parseInt(doc.getElementsByTagName("width")[0].textContent); + const height = parseInt(doc.getElementsByTagName("height")[0].textContent); + const f = parseFloat(doc.getElementsByTagName("f")[0].textContent); + + let a = (height / 2) / f; + let fov = 2 * MathUtils.radToDeg(Math.atan(a)); + + const params = { + path: path, + width: width, + height: height, + f: f, + fov: fov, + }; + + return params; + } + + static async loadImageParams(path){ + + const response = await fetch(path); + if(!response.ok){ + console.error(`failed to load ${path}`); + return; + } + + const content = await response.text(); + const lines = content.split(/\r?\n/); + const imageParams = []; + + for(let i = 1; i < lines.length; i++){ + const line = lines[i]; + + if(line.startsWith("#")){ + continue; + } + + const tokens = line.split(/\s+/); + + if(tokens.length < 6){ + continue; + } + + const params = { + id: tokens[0], + x: Number.parseFloat(tokens[1]), + y: Number.parseFloat(tokens[2]), + z: Number.parseFloat(tokens[3]), + omega: Number.parseFloat(tokens[4]), + phi: Number.parseFloat(tokens[5]), + kappa: Number.parseFloat(tokens[6]), + }; + + // const whitelist = ["47518.jpg"]; + // if(whitelist.includes(params.id)){ + // imageParams.push(params); + // } + imageParams.push(params); + } + + // debug + //return [imageParams[50]]; + + return imageParams; + } + + static async load(cameraParamsPath, imageParamsPath, viewer){ + + const tStart = performance.now(); + + const [cameraParams, imageParams] = await Promise.all([ + OrientedImageLoader.loadCameraParams(cameraParamsPath), + OrientedImageLoader.loadImageParams(imageParamsPath), + ]); + + const orientedImageControls = new OrientedImageControls(viewer); + const raycaster = new Raycaster(); + + const tEnd = performance.now(); + console.log(tEnd - tStart); + + // const sp = new THREE.PlaneGeometry(1, 1); + // const lg = new THREE.Geometry(); + + // lg.vertices.push( + // new THREE.Vector3(-0.5, -0.5, 0), + // new THREE.Vector3( 0.5, -0.5, 0), + // new THREE.Vector3( 0.5, 0.5, 0), + // new THREE.Vector3(-0.5, 0.5, 0), + // new THREE.Vector3(-0.5, -0.5, 0), + // ); + + const {width, height} = cameraParams; + const orientedImages = []; + const sceneNode = new Object3D(); + sceneNode.name = "oriented_images"; + + for(const params of imageParams){ + + // const material = createMaterial(); + // const lm = new THREE.LineBasicMaterial( { color: 0x00ff00 } ); + // const mesh = new THREE.Mesh(sp, material); + + const {x, y, z, omega, phi, kappa} = params; + // const [rx, ry, rz] = [omega, phi, kappa] + // .map(THREE.Math.degToRad); + + // mesh.position.set(x, y, z); + // mesh.scale.set(width / height, 1, 1); + // mesh.rotation.set(rx, ry, rz); + // { + // mesh.updateMatrixWorld(); + // const dir = mesh.getWorldDirection(); + // const alpha = THREE.Math.degToRad(cameraParams.fov / 2); + // const d = -0.5 / Math.tan(alpha); + // const move = dir.clone().multiplyScalar(d); + // mesh.position.add(move); + // } + // sceneNode.add(mesh); + + // const line = new THREE.Line(lg, lm); + // line.position.copy(mesh.position); + // line.scale.copy(mesh.scale); + // line.rotation.copy(mesh.rotation); + // sceneNode.add(line); + + let orientedImage = new OrientedImage(params.id); + // orientedImage.setPosition(x, y, z); + // orientedImage.setRotation(omega, phi, kappa); + // orientedImage.setDimension(width, height); + let position = [x, y, z]; + let rotation = [omega, phi, kappa]; + let dimension = [width, height]; + orientedImage.set(position, rotation, dimension, cameraParams.fov); + + sceneNode.add(orientedImage.mesh); + sceneNode.add(orientedImage.line); + + orientedImages.push(orientedImage); + } + + let hoveredElement = null; + let clipVolume = null; + + const onMouseMove = (evt) => { + const tStart = performance.now(); + if(hoveredElement){ + hoveredElement.line.material.color.setRGB(0, 1, 0); + } + evt.preventDefault(); + + //var array = getMousePosition( container, evt.clientX, evt.clientY ); + const rect = viewer.renderer.domElement.getBoundingClientRect(); + const [x, y] = [evt.clientX, evt.clientY]; + const array = [ + ( x - rect.left ) / rect.width, + ( y - rect.top ) / rect.height + ]; + const onClickPosition = new Vector2(...array); + //const intersects = getIntersects(onClickPosition, scene.children); + const camera = viewer.scene.getActiveCamera(); + const mouse = new Vector3( + + ( onClickPosition.x * 2 ) - 1, + - ( onClickPosition.y * 2 ) + 1 ); + const objects = orientedImages.map(i => i.mesh); + raycaster.setFromCamera( mouse, camera ); + const intersects = raycaster.intersectObjects( objects ); + let selectionChanged = false; + + if ( intersects.length > 0){ + //console.log(intersects); + const intersection = intersects[0]; + const orientedImage = intersection.object.orientedImage; + orientedImage.line.material.color.setRGB(1, 0, 0); + selectionChanged = hoveredElement !== orientedImage; + hoveredElement = orientedImage; + }else { + hoveredElement = null; + } + + let shouldRemoveClipVolume = clipVolume !== null && hoveredElement === null; + let shouldAddClipVolume = clipVolume === null && hoveredElement !== null; + + if(clipVolume !== null && (hoveredElement === null || selectionChanged)){ + // remove existing + viewer.scene.removePolygonClipVolume(clipVolume); + clipVolume = null; + } + + if(shouldAddClipVolume || selectionChanged){ + const img = hoveredElement; + const fov = cameraParams.fov; + const aspect = cameraParams.width / cameraParams.height; + const near = 1.0; + const far = 1000 * 1000; + const camera = new PerspectiveCamera(fov, aspect, near, far); + camera.rotation.order = viewer.scene.getActiveCamera().rotation.order; + camera.rotation.copy(img.mesh.rotation); + { + const mesh = img.mesh; + const dir = mesh.getWorldDirection(); + const pos = mesh.position; + const alpha = MathUtils.degToRad(fov / 2); + const d = 0.5 / Math.tan(alpha); + const newCamPos = pos.clone().add(dir.clone().multiplyScalar(d)); + const newCamDir = pos.clone().sub(newCamPos); + const newCamTarget = new Vector3().addVectors( + newCamPos, + newCamDir.clone().multiplyScalar(viewer.getMoveSpeed())); + camera.position.copy(newCamPos); + } + let volume = new Potree.PolygonClipVolume(camera); + let m0 = new Mesh(); + let m1 = new Mesh(); + let m2 = new Mesh(); + let m3 = new Mesh(); + m0.position.set(-1, -1, 0); + m1.position.set( 1, -1, 0); + m2.position.set( 1, 1, 0); + m3.position.set(-1, 1, 0); + volume.markers.push(m0, m1, m2, m3); + volume.initialized = true; + + viewer.scene.addPolygonClipVolume(volume); + clipVolume = volume; + } + const tEnd = performance.now(); + //console.log(tEnd - tStart); + }; + + const moveToImage = (image) => { + console.log("move to image " + image.id); + + const mesh = image.mesh; + const newCamPos = image.position.clone(); + const newCamTarget = mesh.position.clone(); + + viewer.scene.view.setView(newCamPos, newCamTarget, 500, () => { + orientedImageControls.capture(image); + }); + + if(image.texture === null){ + + const target = image; + + const tmpImagePath = `${Potree.resourcePath}/images/loading.jpg`; + new TextureLoader().load(tmpImagePath, + (texture) => { + if(target.texture === null){ + target.texture = texture; + target.mesh.material.uniforms.tColor.value = texture; + mesh.material.needsUpdate = true; + } + } + ); + + const imagePath = `${imageParamsPath}/../${target.id}`; + new TextureLoader().load(imagePath, + (texture) => { + target.texture = texture; + target.mesh.material.uniforms.tColor.value = texture; + mesh.material.needsUpdate = true; + } + ); + + + } + }; + + const onMouseClick = (evt) => { + + if(orientedImageControls.hasSomethingCaptured()){ + return; + } + + if(hoveredElement){ + moveToImage(hoveredElement); + } + }; + viewer.renderer.domElement.addEventListener( 'mousemove', onMouseMove, false ); + viewer.renderer.domElement.addEventListener( 'mousedown', onMouseClick, false ); + + viewer.addEventListener("update", () => { + + for(const image of orientedImages){ + const world = image.mesh.matrixWorld; + const {width, height} = image; + const aspect = width / height; + + const camera = viewer.scene.getActiveCamera(); + + const imgPos = image.mesh.getWorldPosition(new Vector3()); + const camPos = camera.position; + const d = camPos.distanceTo(imgPos); + + const minSize = 1; // in degrees of fov + const a = MathUtils.degToRad(minSize); + let r = d * Math.tan(a); + r = Math.max(r, 1); + + + image.mesh.scale.set(r * aspect, r, 1); + image.line.scale.set(r * aspect, r, 1); + + image.mesh.material.uniforms.uNear.value = camera.near; + + } + + }); + + const images = new OrientedImages(); + images.node = sceneNode; + images.cameraParamsPath = cameraParamsPath; + images.imageParamsPath = imageParamsPath; + images.cameraParams = cameraParams; + images.imageParams = imageParams; + images.images = orientedImages; + + Potree.debug.moveToImage = moveToImage; + + return images; + } + } + + let sg = new SphereGeometry(1, 8, 8); + let sgHigh = new SphereGeometry(1, 128, 128); + + let sm = new MeshBasicMaterial({side: BackSide}); + let smHovered = new MeshBasicMaterial({side: BackSide, color: 0xff0000}); + + let raycaster = new Raycaster(); + let currentlyHovered = null; + + let previousView = { + controls: null, + position: null, + target: null, + }; + + class Image360{ + + constructor(file, time, longitude, latitude, altitude, course, pitch, roll){ + this.file = file; + this.time = time; + this.longitude = longitude; + this.latitude = latitude; + this.altitude = altitude; + this.course = course; + this.pitch = pitch; + this.roll = roll; + this.mesh = null; + } + }; + + class Images360 extends EventDispatcher{ + + constructor(viewer){ + super(); + + this.viewer = viewer; + + this.selectingEnabled = true; + + this.images = []; + this.node = new Object3D(); + + this.sphere = new Mesh(sgHigh, sm); + this.sphere.visible = false; + this.sphere.scale.set(1000, 1000, 1000); + this.node.add(this.sphere); + this._visible = true; + // this.node.add(label); + + this.focusedImage = null; + + let elUnfocus = document.createElement("input"); + elUnfocus.type = "button"; + elUnfocus.value = "unfocus"; + elUnfocus.style.position = "absolute"; + elUnfocus.style.right = "10px"; + elUnfocus.style.bottom = "10px"; + elUnfocus.style.zIndex = "10000"; + elUnfocus.style.fontSize = "2em"; + elUnfocus.addEventListener("click", () => this.unfocus()); + this.elUnfocus = elUnfocus; + + this.domRoot = viewer.renderer.domElement.parentElement; + this.domRoot.appendChild(elUnfocus); + this.elUnfocus.style.display = "none"; + + viewer.addEventListener("update", () => { + this.update(viewer); + }); + viewer.inputHandler.addInputListener(this); + + this.addEventListener("mousedown", () => { + if(currentlyHovered){ + this.focus(currentlyHovered.image360); + } + }); + + }; + + set visible(visible){ + if(this._visible === visible){ + return; + } + + + for(const image of this.images){ + image.mesh.visible = visible && (this.focusedImage == null); + } + + this.sphere.visible = visible && (this.focusedImage != null); + this._visible = visible; + this.dispatchEvent({ + type: "visibility_changed", + images: this, + }); + } + + get visible(){ + return this._visible; + } + + focus(image360){ + if(this.focusedImage !== null){ + this.unfocus(); + } + + previousView = { + controls: this.viewer.controls, + position: this.viewer.scene.view.position.clone(), + target: viewer.scene.view.getPivot(), + }; + + this.viewer.setControls(this.viewer.orbitControls); + this.viewer.orbitControls.doubleClockZoomEnabled = false; + + for(let image of this.images){ + image.mesh.visible = false; + } + + this.selectingEnabled = false; + + this.sphere.visible = false; + + this.load(image360).then( () => { + this.sphere.visible = true; + this.sphere.material.map = image360.texture; + this.sphere.material.needsUpdate = true; + }); + + { // orientation + let {course, pitch, roll} = image360; + this.sphere.rotation.set( + MathUtils.degToRad(+roll + 90), + MathUtils.degToRad(-pitch), + MathUtils.degToRad(-course + 90), + "ZYX" + ); + } + + this.sphere.position.set(...image360.position); + + let target = new Vector3(...image360.position); + let dir = target.clone().sub(viewer.scene.view.position).normalize(); + let move = dir.multiplyScalar(0.000001); + let newCamPos = target.clone().sub(move); + + viewer.scene.view.setView( + newCamPos, + target, + 500 + ); + + this.focusedImage = image360; + + this.elUnfocus.style.display = ""; + } + + unfocus(){ + this.selectingEnabled = true; + + for(let image of this.images){ + image.mesh.visible = true; + } + + let image = this.focusedImage; + + if(image === null){ + return; + } + + + this.sphere.material.map = null; + this.sphere.material.needsUpdate = true; + this.sphere.visible = false; + + let pos = viewer.scene.view.position; + let target = viewer.scene.view.getPivot(); + let dir = target.clone().sub(pos).normalize(); + let move = dir.multiplyScalar(10); + let newCamPos = target.clone().sub(move); + + viewer.orbitControls.doubleClockZoomEnabled = true; + viewer.setControls(previousView.controls); + + viewer.scene.view.setView( + previousView.position, + previousView.target, + 500 + ); + + + this.focusedImage = null; + + this.elUnfocus.style.display = "none"; + } + + load(image360){ + + return new Promise(resolve => { + let texture = new TextureLoader().load(image360.file, resolve); + texture.wrapS = RepeatWrapping; + texture.repeat.x = -1; + + image360.texture = texture; + }); + + } + + handleHovering(){ + let mouse = viewer.inputHandler.mouse; + let camera = viewer.scene.getActiveCamera(); + let domElement = viewer.renderer.domElement; + + let ray = Potree.Utils.mouseToRay(mouse, camera, domElement.clientWidth, domElement.clientHeight); + + // let tStart = performance.now(); + raycaster.ray.copy(ray); + let intersections = raycaster.intersectObjects(this.node.children); + + if(intersections.length === 0){ + // label.visible = false; + + return; + } + + let intersection = intersections[0]; + currentlyHovered = intersection.object; + currentlyHovered.material = smHovered; + + //label.visible = true; + //label.setText(currentlyHovered.image360.file); + //currentlyHovered.getWorldPosition(label.position); + } + + update(){ + + let {viewer} = this; + + if(currentlyHovered){ + currentlyHovered.material = sm; + currentlyHovered = null; + } + + if(this.selectingEnabled){ + this.handleHovering(); + } + + } + + }; + + + class Images360Loader{ + + static async load(url, viewer, params = {}){ + + if(!params.transform){ + params.transform = { + forward: a => a, + }; + } + + let response = await fetch(`${url}/coordinates.txt`); + let text = await response.text(); + + let lines = text.split(/\r?\n/); + let coordinateLines = lines.slice(1); + + let images360 = new Images360(viewer); + + for(let line of coordinateLines){ + + if(line.trim().length === 0){ + continue; + } + + let tokens = line.split(/\t/); + + let [filename, time, long, lat, alt, course, pitch, roll] = tokens; + time = parseFloat(time); + long = parseFloat(long); + lat = parseFloat(lat); + alt = parseFloat(alt); + course = parseFloat(course); + pitch = parseFloat(pitch); + roll = parseFloat(roll); + + filename = filename.replace(/"/g, ""); + let file = `${url}/${filename}`; + + let image360 = new Image360(file, time, long, lat, alt, course, pitch, roll); + + let xy = params.transform.forward([long, lat]); + let position = [...xy, alt]; + image360.position = position; + + images360.images.push(image360); + } + + Images360Loader.createSceneNodes(images360, params.transform); + + return images360; + + } + + static createSceneNodes(images360, transform){ + + for(let image360 of images360.images){ + let {longitude, latitude, altitude} = image360; + let xy = transform.forward([longitude, latitude]); + + let mesh = new Mesh(sg, sm); + mesh.position.set(...xy, altitude); + mesh.scale.set(1, 1, 1); + mesh.material.transparent = true; + mesh.material.opacity = 0.75; + mesh.image360 = image360; + + { // orientation + var {course, pitch, roll} = image360; + mesh.rotation.set( + MathUtils.degToRad(+roll + 90), + MathUtils.degToRad(-pitch), + MathUtils.degToRad(-course + 90), + "ZYX" + ); + } + + images360.node.add(mesh); + + image360.mesh = mesh; + } + } + + + }; // This is a generated file. Do not edit. @@ -86994,644 +86994,644 @@ ENDSEC } )(); - let fakeCam = new PerspectiveCamera(); - - function toScene(vec, ref){ - let node = ref.clone(); - node.updateMatrix(); - node.updateMatrixWorld(); - - let result = vec.clone().applyMatrix4(node.matrix); - result.z -= 0.8 * node.scale.x; - - return result; - }; - - function computeMove(vrControls, controller){ - - if(!controller || !controller.inputSource || !controller.inputSource.gamepad){ - return null; - } - - let pad = controller.inputSource.gamepad; - - let axes = pad.axes; - // [0,1] are for touchpad, [2,3] for thumbsticks? - let y = 0; - if(axes.length === 2){ - y = axes[1]; - }else if(axes.length === 4){ - y = axes[3]; - } - - y = Math.sign(y) * (2 * y) ** 2; - - let maxSize = 0; - for(let pc of viewer.scene.pointclouds){ - let size = pc.boundingBox.min.distanceTo(pc.boundingBox.max); - maxSize = Math.max(maxSize, size); - } - let multiplicator = Math.pow(maxSize, 0.5) / 2; - - let scale = vrControls.node.scale.x; - let moveSpeed = viewer.getMoveSpeed(); - let amount = multiplicator * y * (moveSpeed ** 0.5) / scale; - - - let rotation = new Quaternion().setFromEuler(controller.rotation); - let dir = new Vector3(0, 0, -1); - dir.applyQuaternion(rotation); - - let move = dir.clone().multiplyScalar(amount); - - let p1 = vrControls.toScene(controller.position); - let p2 = vrControls.toScene(controller.position.clone().add(move)); - - move = p2.clone().sub(p1); - - return move; - }; - - - class FlyMode{ - - constructor(vrControls){ - this.moveFactor = 1; - this.dbgLabel = null; - } - - start(vrControls){ - if(!this.dbgLabel){ - this.dbgLabel = new Potree.TextSprite("abc"); - this.dbgLabel.name = "debug label"; - vrControls.viewer.sceneVR.add(this.dbgLabel); - this.dbgLabel.visible = false; - } - } - - end(){ - - } - - update(vrControls, delta){ - - let primary = vrControls.cPrimary; - let secondary = vrControls.cSecondary; - - let move1 = computeMove(vrControls, primary); - let move2 = computeMove(vrControls, secondary); - - - if(!move1){ - move1 = new Vector3(); - } - - if(!move2){ - move2 = new Vector3(); - } - - let move = move1.clone().add(move2); - - move.multiplyScalar(-delta * this.moveFactor); - vrControls.node.position.add(move); - - - let scale = vrControls.node.scale.x; - - let camVR = vrControls.viewer.renderer.xr.getCamera(fakeCam); - - let vrPos = camVR.getWorldPosition(new Vector3()); - let vrDir = camVR.getWorldDirection(new Vector3()); - let vrTarget = vrPos.clone().add(vrDir.multiplyScalar(scale)); - - let scenePos = toScene(vrPos, vrControls.node); - let sceneDir = toScene(vrPos.clone().add(vrDir), vrControls.node).sub(scenePos); - sceneDir.normalize().multiplyScalar(scale); - let sceneTarget = scenePos.clone().add(sceneDir); - - vrControls.viewer.scene.view.setView(scenePos, sceneTarget); - - if(Potree.debug.message){ - this.dbgLabel.visible = true; - this.dbgLabel.setText(Potree.debug.message); - this.dbgLabel.scale.set(0.1, 0.1, 0.1); - this.dbgLabel.position.copy(primary.position); - } - } - }; - - class TranslationMode{ - - constructor(){ - this.controller = null; - this.startPos = null; - this.debugLine = null; - } - - start(vrControls){ - this.controller = vrControls.triggered.values().next().value; - this.startPos = vrControls.node.position.clone(); - } - - end(vrControls){ - - } - - update(vrControls, delta){ - - let start = this.controller.start.position; - let end = this.controller.position; - - start = vrControls.toScene(start); - end = vrControls.toScene(end); - - let diff = end.clone().sub(start); - diff.set(-diff.x, -diff.y, -diff.z); - - let pos = new Vector3().addVectors(this.startPos, diff); - - vrControls.node.position.copy(pos); - } - - }; - - class RotScaleMode{ - - constructor(){ - this.line = null; - this.startState = null; - } - - start(vrControls){ - if(!this.line){ - this.line = Potree.Utils.debugLine( - vrControls.viewer.sceneVR, - new Vector3(0, 0, 0), - new Vector3(0, 0, 0), - 0xffff00, - ); - - this.dbgLabel = new Potree.TextSprite("abc"); - this.dbgLabel.scale.set(0.1, 0.1, 0.1); - vrControls.viewer.sceneVR.add(this.dbgLabel); - } - - this.line.node.visible = true; - - this.startState = vrControls.node.clone(); - } - - end(vrControls){ - this.line.node.visible = false; - this.dbgLabel.visible = false; - } - - update(vrControls, delta){ - - let start_c1 = vrControls.cPrimary.start.position.clone(); - let start_c2 = vrControls.cSecondary.start.position.clone(); - let start_center = start_c1.clone().add(start_c2).multiplyScalar(0.5); - let start_c1_c2 = start_c2.clone().sub(start_c1); - let end_c1 = vrControls.cPrimary.position.clone(); - let end_c2 = vrControls.cSecondary.position.clone(); - let end_center = end_c1.clone().add(end_c2).multiplyScalar(0.5); - let end_c1_c2 = end_c2.clone().sub(end_c1); - - let d1 = start_c1_c2.length(); - let d2 = end_c1_c2.length(); - - let angleStart = new Vector2(start_c1_c2.x, start_c1_c2.z).angle(); - let angleEnd = new Vector2(end_c1_c2.x, end_c1_c2.z).angle(); - let angleDiff = angleEnd - angleStart; - - let scale = d2 / d1; - - let node = this.startState.clone(); - node.updateMatrix(); - node.matrixAutoUpdate = false; - - let mToOrigin = new Matrix4().makeTranslation(...toScene(start_center, this.startState).multiplyScalar(-1).toArray()); - let mToStart = new Matrix4().makeTranslation(...toScene(start_center, this.startState).toArray()); - let mRotate = new Matrix4().makeRotationZ(angleDiff); - let mScale = new Matrix4().makeScale(1 / scale, 1 / scale, 1 / scale); - - node.applyMatrix4(mToOrigin); - node.applyMatrix4(mRotate); - node.applyMatrix4(mScale); - node.applyMatrix4(mToStart); - - let oldScenePos = toScene(start_center, this.startState); - let newScenePos = toScene(end_center, node); - let toNew = oldScenePos.clone().sub(newScenePos); - let mToNew = new Matrix4().makeTranslation(...toNew.toArray()); - node.applyMatrix4(mToNew); - - node.matrix.decompose(node.position, node.quaternion, node.scale ); - - vrControls.node.position.copy(node.position); - vrControls.node.quaternion.copy(node.quaternion); - vrControls.node.scale.copy(node.scale); - vrControls.node.updateMatrix(); - - { - let scale = vrControls.node.scale.x; - let camVR = vrControls.viewer.renderer.xr.getCamera(fakeCam); - - let vrPos = camVR.getWorldPosition(new Vector3()); - let vrDir = camVR.getWorldDirection(new Vector3()); - let vrTarget = vrPos.clone().add(vrDir.multiplyScalar(scale)); - - let scenePos = toScene(vrPos, this.startState); - let sceneDir = toScene(vrPos.clone().add(vrDir), this.startState).sub(scenePos); - sceneDir.normalize().multiplyScalar(scale); - let sceneTarget = scenePos.clone().add(sceneDir); - - vrControls.viewer.scene.view.setView(scenePos, sceneTarget); - vrControls.viewer.setMoveSpeed(scale); - } - - { // update "GUI" - this.line.set(end_c1, end_c2); - - let scale = vrControls.node.scale.x; - this.dbgLabel.visible = true; - this.dbgLabel.position.copy(end_center); - this.dbgLabel.setText(`scale: 1 : ${scale.toFixed(2)}`); - this.dbgLabel.scale.set(0.05, 0.05, 0.05); - } - - } - - }; - - - class VRControls extends EventDispatcher{ - - constructor(viewer){ - super(viewer); - - this.viewer = viewer; - - viewer.addEventListener("vr_start", this.onStart.bind(this)); - viewer.addEventListener("vr_end", this.onEnd.bind(this)); - - this.node = new Object3D(); - this.node.up.set(0, 0, 1); - this.triggered = new Set(); - - let xr = viewer.renderer.xr; - - { // lights - - const light = new PointLight( 0xffffff, 5, 0, 1 ); - light.position.set(0, 2, 0); - this.viewer.sceneVR.add(light); - } - - this.menu = null; - - const controllerModelFactory = new XRControllerModelFactory(); - - let sg = new SphereGeometry(1, 32, 32); - let sm = new MeshNormalMaterial(); - - { // setup primary controller - let controller = xr.getController(0); - - let grip = xr.getControllerGrip(0); - grip.name = "grip(0)"; - - // ADD CONTROLLERMODEL - grip.add( controllerModelFactory.createControllerModel( grip ) ); - this.viewer.sceneVR.add(grip); - - // ADD SPHERE - let sphere = new Mesh(sg, sm); - sphere.scale.set(0.005, 0.005, 0.005); - - controller.add(sphere); - controller.visible = true; - this.viewer.sceneVR.add(controller); - - { // ADD LINE - - let lineGeometry = new LineGeometry(); - - lineGeometry.setPositions([ - 0, 0, -0.15, - 0, 0, 0.05, - ]); - - let lineMaterial = new LineMaterial({ - color: 0xff0000, - linewidth: 2, - resolution: new Vector2(1000, 1000), - }); - - const line = new Line2(lineGeometry, lineMaterial); - - controller.add(line); - } - - - controller.addEventListener( 'connected', function ( event ) { - const xrInputSource = event.data; - controller.inputSource = xrInputSource; - // initInfo(controller); - }); - - controller.addEventListener( 'selectstart', () => {this.onTriggerStart(controller);}); - controller.addEventListener( 'selectend', () => {this.onTriggerEnd(controller);}); - - this.cPrimary = controller; - - } - - { // setup secondary controller - let controller = xr.getController(1); - - let grip = xr.getControllerGrip(1); - - // ADD CONTROLLER MODEL - let model = controllerModelFactory.createControllerModel( grip ); - grip.add(model); - this.viewer.sceneVR.add( grip ); - - // ADD SPHERE - let sphere = new Mesh(sg, sm); - sphere.scale.set(0.005, 0.005, 0.005); - controller.add(sphere); - controller.visible = true; - this.viewer.sceneVR.add(controller); - - { // ADD LINE - - let lineGeometry = new LineGeometry(); - - lineGeometry.setPositions([ - 0, 0, -0.15, - 0, 0, 0.05, - ]); - - let lineMaterial = new LineMaterial({ - color: 0xff0000, - linewidth: 2, - resolution: new Vector2(1000, 1000), - }); - - const line = new Line2(lineGeometry, lineMaterial); - - controller.add(line); - } - - controller.addEventListener( 'connected', (event) => { - const xrInputSource = event.data; - controller.inputSource = xrInputSource; - this.initMenu(controller); - }); - - controller.addEventListener( 'selectstart', () => {this.onTriggerStart(controller);}); - controller.addEventListener( 'selectend', () => {this.onTriggerEnd(controller);}); - - this.cSecondary = controller; - } - - this.mode_fly = new FlyMode(); - this.mode_translate = new TranslationMode(); - this.mode_rotScale = new RotScaleMode(); - this.setMode(this.mode_fly); - } - - createSlider(label, min, max){ - - let sg = new SphereGeometry(1, 8, 8); - let cg = new CylinderGeometry(1, 1, 1, 8); - let matHandle = new MeshBasicMaterial({color: 0xff0000}); - let matScale = new MeshBasicMaterial({color: 0xff4444}); - let matValue = new MeshNormalMaterial(); - - let node = new Object3D("slider"); - let nLabel = new Potree.TextSprite(`${label}: 0`); - let nMax = new Mesh(sg, matHandle); - let nMin = new Mesh(sg, matHandle); - let nValue = new Mesh(sg, matValue); - let nScale = new Mesh(cg, matScale); - - nLabel.scale.set(0.2, 0.2, 0.2); - nLabel.position.set(0, 0.35, 0); - - nMax.scale.set(0.02, 0.02, 0.02); - nMax.position.set(0, 0.25, 0); - - nMin.scale.set(0.02, 0.02, 0.02); - nMin.position.set(0, -0.25, 0); - - nValue.scale.set(0.02, 0.02, 0.02); - nValue.position.set(0, 0, 0); - - nScale.scale.set(0.005, 0.5, 0.005); - - node.add(nLabel); - node.add(nMax); - node.add(nMin); - node.add(nValue); - node.add(nScale); - - return node; - } - - createInfo(){ - - let texture = new TextureLoader().load(`${Potree.resourcePath}/images/vr_controller_help.jpg`); - let plane = new PlaneBufferGeometry(1, 1, 1, 1); - let infoMaterial = new MeshBasicMaterial({map: texture}); - let infoNode = new Mesh(plane, infoMaterial); - - return infoNode; - } - - initMenu(controller){ - - if(this.menu){ - return; - } - - let node = new Object3D("vr menu"); - - // let nSlider = this.createSlider("speed", 0, 1); - // let nInfo = this.createInfo(); - - // // node.add(nSlider); - // node.add(nInfo); - - // { - // node.rotation.set(-1.5, 0, 0) - // node.scale.set(0.3, 0.3, 0.3); - // node.position.set(-0.2, -0.002, -0.1) - - // // nInfo.position.set(0.5, 0, 0); - // nInfo.scale.set(0.8, 0.6, 0); - - // // controller.add(node); - // } - - // node.position.set(-0.3, 1.2, 0.2); - // node.scale.set(0.3, 0.2, 0.3); - // node.lookAt(new THREE.Vector3(0, 1.5, 0.1)); - - // this.viewer.sceneVR.add(node); - - this.menu = node; - - // window.vrSlider = nSlider; - window.vrMenu = node; - - } - - - toScene(vec){ - let camVR = this.getCamera(); - - let mat = camVR.matrixWorld; - let result = vec.clone().applyMatrix4(mat); - - return result; - } - - toVR(vec){ - let camVR = this.getCamera(); - - let mat = camVR.matrixWorld.clone(); - mat.invert(); - let result = vec.clone().applyMatrix4(mat); - - return result; - } - - setMode(mode){ - - if(this.mode === mode){ - return; - } - - if(this.mode){ - this.mode.end(this); - } - - for(let controller of [this.cPrimary, this.cSecondary]){ - - let start = { - position: controller.position.clone(), - rotation: controller.rotation.clone(), - }; - - controller.start = start; - } - - this.mode = mode; - this.mode.start(this); - } - - onTriggerStart(controller){ - this.triggered.add(controller); - - if(this.triggered.size === 0){ - this.setMode(this.mode_fly); - }else if(this.triggered.size === 1){ - this.setMode(this.mode_translate); - }else if(this.triggered.size === 2){ - this.setMode(this.mode_rotScale); - } - } - - onTriggerEnd(controller){ - this.triggered.delete(controller); - - if(this.triggered.size === 0){ - this.setMode(this.mode_fly); - }else if(this.triggered.size === 1){ - this.setMode(this.mode_translate); - }else if(this.triggered.size === 2){ - this.setMode(this.mode_rotScale); - } - } - - onStart(){ - - let position = this.viewer.scene.view.position.clone(); - let direction = this.viewer.scene.view.direction; - direction.multiplyScalar(-1); - - let target = position.clone().add(direction); - target.z = position.z; - - let scale = this.viewer.getMoveSpeed(); - - this.node.position.copy(position); - this.node.lookAt(target); - this.node.scale.set(scale, scale, scale); - this.node.updateMatrix(); - this.node.updateMatrixWorld(); - } - - onEnd(){ - - } - - - setScene(scene){ - this.scene = scene; - } - - getCamera(){ - let reference = this.viewer.scene.getActiveCamera(); - let camera = new PerspectiveCamera(); - - // let scale = this.node.scale.x; - let scale = this.viewer.getMoveSpeed(); - //camera.near = 0.01 / scale; - camera.near = 0.1; - camera.far = 1000; - // camera.near = reference.near / scale; - // camera.far = reference.far / scale; - camera.up.set(0, 0, 1); - camera.lookAt(new Vector3(0, -1, 0)); - camera.updateMatrix(); - camera.updateMatrixWorld(); - - camera.position.copy(this.node.position); - camera.rotation.copy(this.node.rotation); - camera.scale.set(scale, scale, scale); - camera.updateMatrix(); - camera.updateMatrixWorld(); - camera.matrixAutoUpdate = false; - camera.parent = camera; - - return camera; - } - - update(delta){ - - - - // if(this.mode === this.mode_fly){ - // let ray = new THREE.Ray(origin, direction); - - // for(let object of this.selectables){ - - // if(object.intersectsRay(ray)){ - // object.onHit(ray); - // } - - // } - - // } - - this.mode.update(this, delta); - - - - } + let fakeCam = new PerspectiveCamera(); + + function toScene(vec, ref){ + let node = ref.clone(); + node.updateMatrix(); + node.updateMatrixWorld(); + + let result = vec.clone().applyMatrix4(node.matrix); + result.z -= 0.8 * node.scale.x; + + return result; + }; + + function computeMove(vrControls, controller){ + + if(!controller || !controller.inputSource || !controller.inputSource.gamepad){ + return null; + } + + let pad = controller.inputSource.gamepad; + + let axes = pad.axes; + // [0,1] are for touchpad, [2,3] for thumbsticks? + let y = 0; + if(axes.length === 2){ + y = axes[1]; + }else if(axes.length === 4){ + y = axes[3]; + } + + y = Math.sign(y) * (2 * y) ** 2; + + let maxSize = 0; + for(let pc of viewer.scene.pointclouds){ + let size = pc.boundingBox.min.distanceTo(pc.boundingBox.max); + maxSize = Math.max(maxSize, size); + } + let multiplicator = Math.pow(maxSize, 0.5) / 2; + + let scale = vrControls.node.scale.x; + let moveSpeed = viewer.getMoveSpeed(); + let amount = multiplicator * y * (moveSpeed ** 0.5) / scale; + + + let rotation = new Quaternion().setFromEuler(controller.rotation); + let dir = new Vector3(0, 0, -1); + dir.applyQuaternion(rotation); + + let move = dir.clone().multiplyScalar(amount); + + let p1 = vrControls.toScene(controller.position); + let p2 = vrControls.toScene(controller.position.clone().add(move)); + + move = p2.clone().sub(p1); + + return move; + }; + + + class FlyMode{ + + constructor(vrControls){ + this.moveFactor = 1; + this.dbgLabel = null; + } + + start(vrControls){ + if(!this.dbgLabel){ + this.dbgLabel = new Potree.TextSprite("abc"); + this.dbgLabel.name = "debug label"; + vrControls.viewer.sceneVR.add(this.dbgLabel); + this.dbgLabel.visible = false; + } + } + + end(){ + + } + + update(vrControls, delta){ + + let primary = vrControls.cPrimary; + let secondary = vrControls.cSecondary; + + let move1 = computeMove(vrControls, primary); + let move2 = computeMove(vrControls, secondary); + + + if(!move1){ + move1 = new Vector3(); + } + + if(!move2){ + move2 = new Vector3(); + } + + let move = move1.clone().add(move2); + + move.multiplyScalar(-delta * this.moveFactor); + vrControls.node.position.add(move); + + + let scale = vrControls.node.scale.x; + + let camVR = vrControls.viewer.renderer.xr.getCamera(fakeCam); + + let vrPos = camVR.getWorldPosition(new Vector3()); + let vrDir = camVR.getWorldDirection(new Vector3()); + let vrTarget = vrPos.clone().add(vrDir.multiplyScalar(scale)); + + let scenePos = toScene(vrPos, vrControls.node); + let sceneDir = toScene(vrPos.clone().add(vrDir), vrControls.node).sub(scenePos); + sceneDir.normalize().multiplyScalar(scale); + let sceneTarget = scenePos.clone().add(sceneDir); + + vrControls.viewer.scene.view.setView(scenePos, sceneTarget); + + if(Potree.debug.message){ + this.dbgLabel.visible = true; + this.dbgLabel.setText(Potree.debug.message); + this.dbgLabel.scale.set(0.1, 0.1, 0.1); + this.dbgLabel.position.copy(primary.position); + } + } + }; + + class TranslationMode{ + + constructor(){ + this.controller = null; + this.startPos = null; + this.debugLine = null; + } + + start(vrControls){ + this.controller = vrControls.triggered.values().next().value; + this.startPos = vrControls.node.position.clone(); + } + + end(vrControls){ + + } + + update(vrControls, delta){ + + let start = this.controller.start.position; + let end = this.controller.position; + + start = vrControls.toScene(start); + end = vrControls.toScene(end); + + let diff = end.clone().sub(start); + diff.set(-diff.x, -diff.y, -diff.z); + + let pos = new Vector3().addVectors(this.startPos, diff); + + vrControls.node.position.copy(pos); + } + + }; + + class RotScaleMode{ + + constructor(){ + this.line = null; + this.startState = null; + } + + start(vrControls){ + if(!this.line){ + this.line = Potree.Utils.debugLine( + vrControls.viewer.sceneVR, + new Vector3(0, 0, 0), + new Vector3(0, 0, 0), + 0xffff00, + ); + + this.dbgLabel = new Potree.TextSprite("abc"); + this.dbgLabel.scale.set(0.1, 0.1, 0.1); + vrControls.viewer.sceneVR.add(this.dbgLabel); + } + + this.line.node.visible = true; + + this.startState = vrControls.node.clone(); + } + + end(vrControls){ + this.line.node.visible = false; + this.dbgLabel.visible = false; + } + + update(vrControls, delta){ + + let start_c1 = vrControls.cPrimary.start.position.clone(); + let start_c2 = vrControls.cSecondary.start.position.clone(); + let start_center = start_c1.clone().add(start_c2).multiplyScalar(0.5); + let start_c1_c2 = start_c2.clone().sub(start_c1); + let end_c1 = vrControls.cPrimary.position.clone(); + let end_c2 = vrControls.cSecondary.position.clone(); + let end_center = end_c1.clone().add(end_c2).multiplyScalar(0.5); + let end_c1_c2 = end_c2.clone().sub(end_c1); + + let d1 = start_c1_c2.length(); + let d2 = end_c1_c2.length(); + + let angleStart = new Vector2(start_c1_c2.x, start_c1_c2.z).angle(); + let angleEnd = new Vector2(end_c1_c2.x, end_c1_c2.z).angle(); + let angleDiff = angleEnd - angleStart; + + let scale = d2 / d1; + + let node = this.startState.clone(); + node.updateMatrix(); + node.matrixAutoUpdate = false; + + let mToOrigin = new Matrix4().makeTranslation(...toScene(start_center, this.startState).multiplyScalar(-1).toArray()); + let mToStart = new Matrix4().makeTranslation(...toScene(start_center, this.startState).toArray()); + let mRotate = new Matrix4().makeRotationZ(angleDiff); + let mScale = new Matrix4().makeScale(1 / scale, 1 / scale, 1 / scale); + + node.applyMatrix4(mToOrigin); + node.applyMatrix4(mRotate); + node.applyMatrix4(mScale); + node.applyMatrix4(mToStart); + + let oldScenePos = toScene(start_center, this.startState); + let newScenePos = toScene(end_center, node); + let toNew = oldScenePos.clone().sub(newScenePos); + let mToNew = new Matrix4().makeTranslation(...toNew.toArray()); + node.applyMatrix4(mToNew); + + node.matrix.decompose(node.position, node.quaternion, node.scale ); + + vrControls.node.position.copy(node.position); + vrControls.node.quaternion.copy(node.quaternion); + vrControls.node.scale.copy(node.scale); + vrControls.node.updateMatrix(); + + { + let scale = vrControls.node.scale.x; + let camVR = vrControls.viewer.renderer.xr.getCamera(fakeCam); + + let vrPos = camVR.getWorldPosition(new Vector3()); + let vrDir = camVR.getWorldDirection(new Vector3()); + let vrTarget = vrPos.clone().add(vrDir.multiplyScalar(scale)); + + let scenePos = toScene(vrPos, this.startState); + let sceneDir = toScene(vrPos.clone().add(vrDir), this.startState).sub(scenePos); + sceneDir.normalize().multiplyScalar(scale); + let sceneTarget = scenePos.clone().add(sceneDir); + + vrControls.viewer.scene.view.setView(scenePos, sceneTarget); + vrControls.viewer.setMoveSpeed(scale); + } + + { // update "GUI" + this.line.set(end_c1, end_c2); + + let scale = vrControls.node.scale.x; + this.dbgLabel.visible = true; + this.dbgLabel.position.copy(end_center); + this.dbgLabel.setText(`scale: 1 : ${scale.toFixed(2)}`); + this.dbgLabel.scale.set(0.05, 0.05, 0.05); + } + + } + + }; + + + class VRControls extends EventDispatcher{ + + constructor(viewer){ + super(viewer); + + this.viewer = viewer; + + viewer.addEventListener("vr_start", this.onStart.bind(this)); + viewer.addEventListener("vr_end", this.onEnd.bind(this)); + + this.node = new Object3D(); + this.node.up.set(0, 0, 1); + this.triggered = new Set(); + + let xr = viewer.renderer.xr; + + { // lights + + const light = new PointLight( 0xffffff, 5, 0, 1 ); + light.position.set(0, 2, 0); + this.viewer.sceneVR.add(light); + } + + this.menu = null; + + const controllerModelFactory = new XRControllerModelFactory(); + + let sg = new SphereGeometry(1, 32, 32); + let sm = new MeshNormalMaterial(); + + { // setup primary controller + let controller = xr.getController(0); + + let grip = xr.getControllerGrip(0); + grip.name = "grip(0)"; + + // ADD CONTROLLERMODEL + grip.add( controllerModelFactory.createControllerModel( grip ) ); + this.viewer.sceneVR.add(grip); + + // ADD SPHERE + let sphere = new Mesh(sg, sm); + sphere.scale.set(0.005, 0.005, 0.005); + + controller.add(sphere); + controller.visible = true; + this.viewer.sceneVR.add(controller); + + { // ADD LINE + + let lineGeometry = new LineGeometry(); + + lineGeometry.setPositions([ + 0, 0, -0.15, + 0, 0, 0.05, + ]); + + let lineMaterial = new LineMaterial({ + color: 0xff0000, + linewidth: 2, + resolution: new Vector2(1000, 1000), + }); + + const line = new Line2(lineGeometry, lineMaterial); + + controller.add(line); + } + + + controller.addEventListener( 'connected', function ( event ) { + const xrInputSource = event.data; + controller.inputSource = xrInputSource; + // initInfo(controller); + }); + + controller.addEventListener( 'selectstart', () => {this.onTriggerStart(controller);}); + controller.addEventListener( 'selectend', () => {this.onTriggerEnd(controller);}); + + this.cPrimary = controller; + + } + + { // setup secondary controller + let controller = xr.getController(1); + + let grip = xr.getControllerGrip(1); + + // ADD CONTROLLER MODEL + let model = controllerModelFactory.createControllerModel( grip ); + grip.add(model); + this.viewer.sceneVR.add( grip ); + + // ADD SPHERE + let sphere = new Mesh(sg, sm); + sphere.scale.set(0.005, 0.005, 0.005); + controller.add(sphere); + controller.visible = true; + this.viewer.sceneVR.add(controller); + + { // ADD LINE + + let lineGeometry = new LineGeometry(); + + lineGeometry.setPositions([ + 0, 0, -0.15, + 0, 0, 0.05, + ]); + + let lineMaterial = new LineMaterial({ + color: 0xff0000, + linewidth: 2, + resolution: new Vector2(1000, 1000), + }); + + const line = new Line2(lineGeometry, lineMaterial); + + controller.add(line); + } + + controller.addEventListener( 'connected', (event) => { + const xrInputSource = event.data; + controller.inputSource = xrInputSource; + this.initMenu(controller); + }); + + controller.addEventListener( 'selectstart', () => {this.onTriggerStart(controller);}); + controller.addEventListener( 'selectend', () => {this.onTriggerEnd(controller);}); + + this.cSecondary = controller; + } + + this.mode_fly = new FlyMode(); + this.mode_translate = new TranslationMode(); + this.mode_rotScale = new RotScaleMode(); + this.setMode(this.mode_fly); + } + + createSlider(label, min, max){ + + let sg = new SphereGeometry(1, 8, 8); + let cg = new CylinderGeometry(1, 1, 1, 8); + let matHandle = new MeshBasicMaterial({color: 0xff0000}); + let matScale = new MeshBasicMaterial({color: 0xff4444}); + let matValue = new MeshNormalMaterial(); + + let node = new Object3D("slider"); + let nLabel = new Potree.TextSprite(`${label}: 0`); + let nMax = new Mesh(sg, matHandle); + let nMin = new Mesh(sg, matHandle); + let nValue = new Mesh(sg, matValue); + let nScale = new Mesh(cg, matScale); + + nLabel.scale.set(0.2, 0.2, 0.2); + nLabel.position.set(0, 0.35, 0); + + nMax.scale.set(0.02, 0.02, 0.02); + nMax.position.set(0, 0.25, 0); + + nMin.scale.set(0.02, 0.02, 0.02); + nMin.position.set(0, -0.25, 0); + + nValue.scale.set(0.02, 0.02, 0.02); + nValue.position.set(0, 0, 0); + + nScale.scale.set(0.005, 0.5, 0.005); + + node.add(nLabel); + node.add(nMax); + node.add(nMin); + node.add(nValue); + node.add(nScale); + + return node; + } + + createInfo(){ + + let texture = new TextureLoader().load(`${Potree.resourcePath}/images/vr_controller_help.jpg`); + let plane = new PlaneBufferGeometry(1, 1, 1, 1); + let infoMaterial = new MeshBasicMaterial({map: texture}); + let infoNode = new Mesh(plane, infoMaterial); + + return infoNode; + } + + initMenu(controller){ + + if(this.menu){ + return; + } + + let node = new Object3D("vr menu"); + + // let nSlider = this.createSlider("speed", 0, 1); + // let nInfo = this.createInfo(); + + // // node.add(nSlider); + // node.add(nInfo); + + // { + // node.rotation.set(-1.5, 0, 0) + // node.scale.set(0.3, 0.3, 0.3); + // node.position.set(-0.2, -0.002, -0.1) + + // // nInfo.position.set(0.5, 0, 0); + // nInfo.scale.set(0.8, 0.6, 0); + + // // controller.add(node); + // } + + // node.position.set(-0.3, 1.2, 0.2); + // node.scale.set(0.3, 0.2, 0.3); + // node.lookAt(new THREE.Vector3(0, 1.5, 0.1)); + + // this.viewer.sceneVR.add(node); + + this.menu = node; + + // window.vrSlider = nSlider; + window.vrMenu = node; + + } + + + toScene(vec){ + let camVR = this.getCamera(); + + let mat = camVR.matrixWorld; + let result = vec.clone().applyMatrix4(mat); + + return result; + } + + toVR(vec){ + let camVR = this.getCamera(); + + let mat = camVR.matrixWorld.clone(); + mat.invert(); + let result = vec.clone().applyMatrix4(mat); + + return result; + } + + setMode(mode){ + + if(this.mode === mode){ + return; + } + + if(this.mode){ + this.mode.end(this); + } + + for(let controller of [this.cPrimary, this.cSecondary]){ + + let start = { + position: controller.position.clone(), + rotation: controller.rotation.clone(), + }; + + controller.start = start; + } + + this.mode = mode; + this.mode.start(this); + } + + onTriggerStart(controller){ + this.triggered.add(controller); + + if(this.triggered.size === 0){ + this.setMode(this.mode_fly); + }else if(this.triggered.size === 1){ + this.setMode(this.mode_translate); + }else if(this.triggered.size === 2){ + this.setMode(this.mode_rotScale); + } + } + + onTriggerEnd(controller){ + this.triggered.delete(controller); + + if(this.triggered.size === 0){ + this.setMode(this.mode_fly); + }else if(this.triggered.size === 1){ + this.setMode(this.mode_translate); + }else if(this.triggered.size === 2){ + this.setMode(this.mode_rotScale); + } + } + + onStart(){ + + let position = this.viewer.scene.view.position.clone(); + let direction = this.viewer.scene.view.direction; + direction.multiplyScalar(-1); + + let target = position.clone().add(direction); + target.z = position.z; + + let scale = this.viewer.getMoveSpeed(); + + this.node.position.copy(position); + this.node.lookAt(target); + this.node.scale.set(scale, scale, scale); + this.node.updateMatrix(); + this.node.updateMatrixWorld(); + } + + onEnd(){ + + } + + + setScene(scene){ + this.scene = scene; + } + + getCamera(){ + let reference = this.viewer.scene.getActiveCamera(); + let camera = new PerspectiveCamera(); + + // let scale = this.node.scale.x; + let scale = this.viewer.getMoveSpeed(); + //camera.near = 0.01 / scale; + camera.near = 0.1; + camera.far = 1000; + // camera.near = reference.near / scale; + // camera.far = reference.far / scale; + camera.up.set(0, 0, 1); + camera.lookAt(new Vector3(0, -1, 0)); + camera.updateMatrix(); + camera.updateMatrixWorld(); + + camera.position.copy(this.node.position); + camera.rotation.copy(this.node.rotation); + camera.scale.set(scale, scale, scale); + camera.updateMatrix(); + camera.updateMatrixWorld(); + camera.matrixAutoUpdate = false; + camera.parent = camera; + + return camera; + } + + update(delta){ + + + + // if(this.mode === this.mode_fly){ + // let ray = new THREE.Ray(origin, direction); + + // for(let object of this.selectables){ + + // if(object.intersectsRay(ray)){ + // object.onHit(ray); + // } + + // } + + // } + + this.mode.update(this, delta); + + + + } }; // Adapted from three.js VRButton diff --git a/app/static/app/js/vendor/potree/build/potree/sidebar.html b/app/static/app/js/vendor/potree/build/potree/sidebar.html index 2b3255aa..67e8d053 100644 --- a/app/static/app/js/vendor/potree/build/potree/sidebar.html +++ b/app/static/app/js/vendor/potree/build/potree/sidebar.html @@ -15,14 +15,14 @@
-

Cameras

+

Cameras

-

Textured Model

+

Textured Model

  • diff --git a/package.json b/package.json index 95782536..b4d53d04 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "d3": "^3.5.5", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.15.1", - "epsg": "^0.5.0", "exifr": "^6.0.0", "extract-text-webpack-plugin": "^4.0.0-beta.0", "fbemitter": "^2.1.1",