From d358029a1c81a882df116b112f026d05ef824bd5 Mon Sep 17 00:00:00 2001 From: ftoromanoff Date: Thu, 27 Feb 2025 14:13:33 +0100 Subject: [PATCH] fix(Potree1&2): linked to reprojection --- examples/potree2_25d_map.html | 20 +++++---- examples/potree_25d_map.html | 33 +++++++++------ src/Core/Potree2Node.js | 65 ++++++++++++++++++------------ src/Core/PotreeNode.js | 56 ++++++++++++++----------- src/Layer/Potree2Layer.js | 5 +++ src/Layer/PotreeLayer.js | 5 +++ src/Parser/Potree2BinParser.js | 7 +++- src/Parser/PotreeBinParser.js | 4 ++ src/Parser/PotreeCinParser.js | 9 ++++- src/Provider/PointCloudProvider.js | 2 +- src/Source/PotreeSource.js | 8 ++++ 11 files changed, 140 insertions(+), 74 deletions(-) diff --git a/examples/potree2_25d_map.html b/examples/potree2_25d_map.html index 7ecb9533d9..dd91ee4b91 100644 --- a/examples/potree2_25d_map.html +++ b/examples/potree2_25d_map.html @@ -64,7 +64,7 @@ source: new itowns.Potree2Source({ file: 'metadata.json', url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds/potree2.0/lion', - crs: view.referenceCrs, + crs: 'EPSG:3946', }), }); @@ -94,22 +94,26 @@ // add potreeLayer to scene function onLayerReady() { - var ratio; var position; var lookAt = new itowns.THREE.Vector3(); var size = new itowns.THREE.Vector3(); - potreeLayer.root.bbox.getSize(size); - potreeLayer.root.bbox.getCenter(lookAt); + potreeLayer.root._bbox.getSize(size); + potreeLayer.root._bbox.getCenter(lookAt); + lookAt.z = potreeLayer.root._bbox.min.z; debug.PointCloudDebug.initTools(view, potreeLayer, debugGui); view.camera.camera3D.far = 2.0 * size.length(); - ratio = size.x / size.z; - position = potreeLayer.root.bbox.min.clone().add( - size.multiply({ x: 0, y: 0, z: ratio * 0.5 })); - lookAt.z = potreeLayer.root.bbox.min.z; + // position = potreeLayer.root.bbox.min.clone().add( + // size.multiply({ x: 0, y: 0, z: (size.x / size.z) * 0.5 })); + + var corner = new itowns.THREE.Vector3(...potreeLayer.source.metadata.boundingBox.min); + var position = corner.clone().add( + size.multiply({ x: 0, y: 0, z: (size.x / size.z) }) + ); + placeCamera(position, lookAt); controls.moveSpeed = size.length() / 3; diff --git a/examples/potree_25d_map.html b/examples/potree_25d_map.html index 74efbfc34e..1917090a47 100644 --- a/examples/potree_25d_map.html +++ b/examples/potree_25d_map.html @@ -60,12 +60,13 @@ view.mainLoop.gfxEngine.renderer.setClearColor(0xcccccc); // Configure Point Cloud layer + const potreeSource = new itowns.PotreeSource({ + file: 'eglise_saint_blaise_arles.js', + url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds/eglise_saint_blaise_arles', + crs: 'EPSG:3946', + }); potreeLayer = new itowns.PotreeLayer('eglise_saint_blaise_arles', { - source: new itowns.PotreeSource({ - file: 'eglise_saint_blaise_arles.js', - url: 'https://raw.githubusercontent.com/iTowns/iTowns2-sample-data/master/pointclouds/eglise_saint_blaise_arles', - crs: view.referenceCrs, - }), + source: potreeSource, }); // point selection on double-click @@ -94,22 +95,30 @@ // add potreeLayer to scene function onLayerReady() { - var ratio; var position; var lookAt = new itowns.THREE.Vector3(); var size = new itowns.THREE.Vector3(); - potreeLayer.root.bbox.getSize(size); - potreeLayer.root.bbox.getCenter(lookAt); + potreeLayer.root._bbox.getSize(size); + potreeLayer.root._bbox.getCenter(lookAt); + lookAt.z = potreeLayer.root._bbox.min.z; debug.PointCloudDebug.initTools(view, potreeLayer, debugGui); view.camera3D.far = 2.0 * size.length(); - ratio = size.x / size.z; - position = potreeLayer.root.bbox.min.clone().add( - size.multiply({ x: 0, y: 0, z: ratio * 0.5 })); - lookAt.z = potreeLayer.root.bbox.min.z; + // position = potreeLayer.root.bbox.min.clone().add( + // size.multiply({ x: 0, y: 0, z: (size.x / size.z) * 0.5 })); + + console.log(potreeLayer.source.boundsConforming); + var corner = new itowns.THREE.Vector3(...potreeLayer.source.boundsConforming.slice(0, 3)); + console.log('corner', corner); + var position = corner.clone().add( + size.multiply({ x: 0, y: 0, z: (size.x / size.z) }) + ); + + console.log(position, lookAt); + placeCamera(position, lookAt); controls.moveSpeed = size.length() / 3; diff --git a/src/Core/Potree2Node.js b/src/Core/Potree2Node.js index 21de2cb172..8c265826f3 100644 --- a/src/Core/Potree2Node.js +++ b/src/Core/Potree2Node.js @@ -62,44 +62,50 @@ class Potree2Node extends PointCloudNode { node.depth = this.depth + 1; } - createChildAABB(node, childIndex) { + createChildAABB(childNode, childIndex) { // Code inspired from potree - node.bbox.copy(this.bbox); - this.bbox.getCenter(node.bbox.max); - dHalfLength.copy(node.bbox.max).sub(this.bbox.min); + childNode._bbox.copy(this._bbox); + this._bbox.getCenter(childNode._bbox.max); + dHalfLength.copy(childNode._bbox.max).sub(this._bbox.min); if (childIndex === 1) { - node.bbox.min.z += dHalfLength.z; - node.bbox.max.z += dHalfLength.z; + childNode._bbox.min.z += dHalfLength.z; + childNode._bbox.max.z += dHalfLength.z; } else if (childIndex === 3) { - node.bbox.min.z += dHalfLength.z; - node.bbox.max.z += dHalfLength.z; - node.bbox.min.y += dHalfLength.y; - node.bbox.max.y += dHalfLength.y; + childNode._bbox.min.z += dHalfLength.z; + childNode._bbox.max.z += dHalfLength.z; + childNode._bbox.min.y += dHalfLength.y; + childNode._bbox.max.y += dHalfLength.y; } else if (childIndex === 0) { // } else if (childIndex === 2) { - node.bbox.min.y += dHalfLength.y; - node.bbox.max.y += dHalfLength.y; + childNode._bbox.min.y += dHalfLength.y; + childNode._bbox.max.y += dHalfLength.y; } else if (childIndex === 5) { - node.bbox.min.z += dHalfLength.z; - node.bbox.max.z += dHalfLength.z; - node.bbox.min.x += dHalfLength.x; - node.bbox.max.x += dHalfLength.x; + childNode._bbox.min.z += dHalfLength.z; + childNode._bbox.max.z += dHalfLength.z; + childNode._bbox.min.x += dHalfLength.x; + childNode._bbox.max.x += dHalfLength.x; } else if (childIndex === 7) { - node.bbox.min.add(dHalfLength); - node.bbox.max.add(dHalfLength); + childNode._bbox.min.add(dHalfLength); + childNode._bbox.max.add(dHalfLength); } else if (childIndex === 4) { - node.bbox.min.x += dHalfLength.x; - node.bbox.max.x += dHalfLength.x; + childNode._bbox.min.x += dHalfLength.x; + childNode._bbox.max.x += dHalfLength.x; } else if (childIndex === 6) { - node.bbox.min.y += dHalfLength.y; - node.bbox.max.y += dHalfLength.y; - node.bbox.min.x += dHalfLength.x; - node.bbox.max.x += dHalfLength.x; + childNode._bbox.min.y += dHalfLength.y; + childNode._bbox.max.y += dHalfLength.y; + childNode._bbox.min.x += dHalfLength.x; + childNode._bbox.max.x += dHalfLength.x; } } + getCenter() { + // for potree we send the point use as origin for the data + // ie the min corner of the bbox + this.center = this._bbox.min; + } + get octreeIsLoaded() { return !(this.childrenBitField && this.children.length === 0); } @@ -133,14 +139,19 @@ class Potree2Node extends PointCloudNode { await this.loadOctree(); } + this.getCenter(); + return this.layer.source.fetcher(this.url, this.networkOptions(this.byteOffset, this.byteSize)) .then(file => this.layer.source.parser(file, { in: { source: this.layer.source, - bbox: this.bbox, + bbox: this._bbox, numPoints: this.numPoints, }, - out: this.layer, + out: { + ...this.layer, + center: this.center, + }, })) .then((data) => { this.loaded = true; @@ -220,6 +231,8 @@ class Potree2Node extends PointCloudNode { } const child = new Potree2Node(numPoints, childMask, this.layer); + child._quaternion = this._quaternion; + child._position = this._position; child.spacing = current.spacing / 2; current.add(child, childIndex); diff --git a/src/Core/PotreeNode.js b/src/Core/PotreeNode.js index 48a9802318..a1a56602a3 100644 --- a/src/Core/PotreeNode.js +++ b/src/Core/PotreeNode.js @@ -26,44 +26,50 @@ class PotreeNode extends PointCloudNode { } } - createChildAABB(node, childIndex) { + createChildAABB(childNode, childIndex) { // Code inspired from potree - node.bbox.copy(this.bbox); - this.bbox.getCenter(node.bbox.max); - dHalfLength.copy(node.bbox.max).sub(this.bbox.min); + childNode._bbox.copy(this._bbox); + this._bbox.getCenter(childNode._bbox.max); + dHalfLength.copy(childNode._bbox.max).sub(this._bbox.min); if (childIndex === 1) { - node.bbox.min.z += dHalfLength.z; - node.bbox.max.z += dHalfLength.z; + childNode._bbox.min.z += dHalfLength.z; + childNode._bbox.max.z += dHalfLength.z; } else if (childIndex === 3) { - node.bbox.min.z += dHalfLength.z; - node.bbox.max.z += dHalfLength.z; - node.bbox.min.y += dHalfLength.y; - node.bbox.max.y += dHalfLength.y; + childNode._bbox.min.z += dHalfLength.z; + childNode._bbox.max.z += dHalfLength.z; + childNode._bbox.min.y += dHalfLength.y; + childNode._bbox.max.y += dHalfLength.y; } else if (childIndex === 0) { // } else if (childIndex === 2) { - node.bbox.min.y += dHalfLength.y; - node.bbox.max.y += dHalfLength.y; + childNode._bbox.min.y += dHalfLength.y; + childNode._bbox.max.y += dHalfLength.y; } else if (childIndex === 5) { - node.bbox.min.z += dHalfLength.z; - node.bbox.max.z += dHalfLength.z; - node.bbox.min.x += dHalfLength.x; - node.bbox.max.x += dHalfLength.x; + childNode._bbox.min.z += dHalfLength.z; + childNode._bbox.max.z += dHalfLength.z; + childNode._bbox.min.x += dHalfLength.x; + childNode._bbox.max.x += dHalfLength.x; } else if (childIndex === 7) { - node.bbox.min.add(dHalfLength); - node.bbox.max.add(dHalfLength); + childNode._bbox.min.add(dHalfLength); + childNode._bbox.max.add(dHalfLength); } else if (childIndex === 4) { - node.bbox.min.x += dHalfLength.x; - node.bbox.max.x += dHalfLength.x; + childNode._bbox.min.x += dHalfLength.x; + childNode._bbox.max.x += dHalfLength.x; } else if (childIndex === 6) { - node.bbox.min.y += dHalfLength.y; - node.bbox.max.y += dHalfLength.y; - node.bbox.min.x += dHalfLength.x; - node.bbox.max.x += dHalfLength.x; + childNode._bbox.min.y += dHalfLength.y; + childNode._bbox.max.y += dHalfLength.y; + childNode._bbox.min.x += dHalfLength.x; + childNode._bbox.max.x += dHalfLength.x; } } + getCenter() { + // for potree we send the point use as origin for the data + // ie the min corner of the bbox + this.center = this._bbox.min; + } + get octreeIsLoaded() { return !(this.childrenBitField && this.children.length === 0); } @@ -93,6 +99,8 @@ class PotreeNode extends PointCloudNode { const childrenBitField = view.getUint8(offset); offset += 1; const numPoints = view.getUint32(offset, true) || this.numPoints; offset += 4; const item = new PotreeNode(numPoints, childrenBitField, this.layer); + item._quaternion = this._quaternion; + item._position = this._position; snode.add(item, indexChild, this); stack.push(item); } diff --git a/src/Layer/Potree2Layer.js b/src/Layer/Potree2Layer.js index 78678daea1..51b06aa065 100644 --- a/src/Layer/Potree2Layer.js +++ b/src/Layer/Potree2Layer.js @@ -181,6 +181,11 @@ class Potree2Layer extends PointCloudLayer { return this.root.loadOctree().then(resolve); }); } + + setRootBbox(min, max) { + this.root._bbox.min.set(...min); + this.root._bbox.max.set(...max); + } } export default Potree2Layer; diff --git a/src/Layer/PotreeLayer.js b/src/Layer/PotreeLayer.js index 372baf8fbd..18a803f572 100644 --- a/src/Layer/PotreeLayer.js +++ b/src/Layer/PotreeLayer.js @@ -73,6 +73,11 @@ class PotreeLayer extends PointCloudLayer { return this.root.loadOctree().then(resolve); }); } + + setRootBbox(min, max) { + this.root._bbox.min.set(...min); + this.root._bbox.max.set(...max); + } } export default PotreeLayer; diff --git a/src/Parser/Potree2BinParser.js b/src/Parser/Potree2BinParser.js index b13341d310..e568eeb77f 100644 --- a/src/Parser/Potree2BinParser.js +++ b/src/Parser/Potree2BinParser.js @@ -37,8 +37,10 @@ export default { * @param {ArrayBuffer} buffer - the bin buffer. * @param {Object} options * @param {string[]} options.in.pointAttributes - the point attributes information contained in metadata.js - * @return {Promise} - a promise that resolves with a THREE.BufferGeometry. + * @param {THREE.Box3} options.in.bbox - the bbox of the node + * @param {THREE.Vector3} options.out.center - the origin position of the data * + * @return {Promise} - a promise that resolves with a THREE.BufferGeometry. */ parse: async function parse(buffer, options) { const metadata = options.in.source.metadata; @@ -95,6 +97,9 @@ export default { } }); + geometry.userData.origin = options.out.center; + geometry.userData.rotation = new THREE.Quaternion(); + geometry.computeBoundingBox(); return { geometry, density: data.density }; diff --git a/src/Parser/PotreeBinParser.js b/src/Parser/PotreeBinParser.js index 996ab8e4eb..742392dc94 100644 --- a/src/Parser/PotreeBinParser.js +++ b/src/Parser/PotreeBinParser.js @@ -69,6 +69,7 @@ export default { * @param {ArrayBuffer} buffer - the bin buffer. * @param {Object} options * @param {string[]} options.in.pointAttributes - the point attributes information contained in cloud.js + * @param {THREE.Vector3} options.out.center - the origin position of the data * @return {Promise} - a promise that resolves with a THREE.BufferGeometry. * */ @@ -103,6 +104,9 @@ export default { geometry.setAttribute(attr.attributeName, new THREE.BufferAttribute(array, attr.numElements, attr.normalized)); } + geometry.userData.origin = options.out.center; + geometry.userData.rotation = new THREE.Quaternion(); + geometry.computeBoundingBox(); return Promise.resolve(geometry); diff --git a/src/Parser/PotreeCinParser.js b/src/Parser/PotreeCinParser.js index 169a658da1..e82be0f34b 100644 --- a/src/Parser/PotreeCinParser.js +++ b/src/Parser/PotreeCinParser.js @@ -5,10 +5,12 @@ export default { /** Parse .cin PotreeConverter format (see {@link https://github.com/peppsac/PotreeConverter/tree/custom_bin}) and convert to a THREE.BufferGeometry * @function parse * @param {ArrayBuffer} buffer - the cin buffer. - * @return {Promise} - a promise that resolves with a THREE.BufferGeometry. + * @param {Object} options + * @param {THREE.Vector3} options.out.center - the origin position of the data * + * @return {Promise} - a promise that resolves with a THREE.BufferGeometry. */ - parse: function parse(buffer) { + parse: function parse(buffer, options) { if (!buffer) { throw new Error('No array buffer provided.'); } @@ -29,6 +31,9 @@ export default { geometry.setAttribute('color', new THREE.BufferAttribute(colors, 4, true)); geometry.boundingBox = box; + geometry.userData.origin = options.out.center; + geometry.userData.rotation = new THREE.Quaternion(); + return Promise.resolve(geometry); }, }; diff --git a/src/Provider/PointCloudProvider.js b/src/Provider/PointCloudProvider.js index a933fc3826..1f4083581e 100644 --- a/src/Provider/PointCloudProvider.js +++ b/src/Provider/PointCloudProvider.js @@ -37,7 +37,7 @@ export default { points.frustumCulled = false; points.matrixAutoUpdate = false; points.scale.copy(layer.scale); - points.position.copy(geometry.userData.origin || node.bbox.min); + points.position.copy(geometry.userData.origin); const quaternion = geometry.userData.rotation.clone().invert(); points.quaternion.copy(quaternion); diff --git a/src/Source/PotreeSource.js b/src/Source/PotreeSource.js index 4229c50d9c..8f3eaa1d92 100644 --- a/src/Source/PotreeSource.js +++ b/src/Source/PotreeSource.js @@ -77,6 +77,14 @@ class PotreeSource extends Source { // https://github.com/PropellerAero/potree-propeller-private/blob/master/docs/file_format.md#cloudjs this.whenReady = (source.cloud ? Promise.resolve(source.cloud) : Fetcher.json(`${this.url}/${this.file}`, this.networkOptions)) .then((cloud) => { + this.boundsConforming = [ + cloud.tightBoundingBox.lx, + cloud.tightBoundingBox.ly, + cloud.tightBoundingBox.lz, + cloud.tightBoundingBox.ux, + cloud.tightBoundingBox.uy, + cloud.tightBoundingBox.uz, + ]; this.pointAttributes = cloud.pointAttributes; this.baseurl = `${this.url}/${cloud.octreeDir}/r`; this.extension = cloud.pointAttributes === 'CIN' ? 'cin' : 'bin';