Skip to content

Commit

Permalink
feat(EntwineData): add obb for reprojection in View crs
Browse files Browse the repository at this point in the history
  • Loading branch information
ftoromanoff committed Oct 11, 2024
1 parent b507e27 commit 52b0639
Show file tree
Hide file tree
Showing 10 changed files with 461 additions and 104 deletions.
3 changes: 2 additions & 1 deletion config/threeExamples.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export default {
'./utils/WorkerPool.js',
'./capabilities/WebGL.js',
'./libs/ktx-parse.module.js',
'./libs/zstddec.module.js'
'./libs/zstddec.module.js',
'./math/OBB.js',
],
};
75 changes: 46 additions & 29 deletions src/Core/EntwinePointTileNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,60 +69,77 @@ class EntwinePointTileNode extends PointCloudNode {
this.url = `${this.layer.source.url}/ept-data/${this.id}.${this.layer.source.extension}`;
}

createChildAABB(node) {
createChildAABB(childNode) {
// factor to apply, based on the depth difference (can be > 1)
const f = 2 ** (node.depth - this.depth);
const f = 2 ** (childNode.depth - this.depth);

// size of the child node bbox (Vector3), based on the size of the
// parent node, and divided by the factor
this.bbox.getSize(size).divideScalar(f);

// initialize the child node bbox at the location of the parent node bbox
node.bbox.min.copy(this.bbox.min);
childNode.bbox.min.copy(this.bbox.min);

// position of the parent node, if it was at the same depth than the
// child, found by multiplying the tree position by the factor
position.copy(this).multiplyScalar(f);

// difference in position between the two nodes, at child depth, and
// scale it using the size
translation.subVectors(node, position).multiply(size);
translation.subVectors(childNode, position).multiply(size);

// apply the translation to the child node bbox
node.bbox.min.add(translation);
childNode.bbox.min.add(translation);

// use the size computed above to set the max
node.bbox.max.copy(node.bbox.min).add(size);
childNode.bbox.max.copy(childNode.bbox.min).add(size);
}

createChildOBB(childNode) {
const f = 2 ** (childNode.depth - this.depth);

this.obb.getSize(size).divideScalar(f);

position.copy(this).multiplyScalar(f);

translation.subVectors(childNode, position).multiply(size);

childNode.obb = this.obb.clone();
childNode.obb.halfSize.divideScalar(f);

childNode.obb.center = this.obb.center.clone().add(this.obb.halfSize.clone().multiplyScalar(-0.5)).add(translation);
childNode.obb.position = this.obb.position.clone();
}

get octreeIsLoaded() {
return this.numPoints >= 0;
}

loadOctree() {
return Fetcher.json(`${this.layer.source.url}/ept-hierarchy/${this.id}.json`, this.layer.source.networkOptions).then((hierarchy) => {
this.numPoints = hierarchy[this.id];

const stack = [];
stack.push(this);

while (stack.length) {
const node = stack.shift();
const depth = node.depth + 1;
const x = node.x * 2;
const y = node.y * 2;
const z = node.z * 2;

node.findAndCreateChild(depth, x, y, z, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y, z, hierarchy, stack);
node.findAndCreateChild(depth, x, y + 1, z, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y + 1, z, hierarchy, stack);
node.findAndCreateChild(depth, x, y, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x, y + 1, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y + 1, z + 1, hierarchy, stack);
}
});
return Fetcher.json(`${this.layer.source.url}/ept-hierarchy/${this.id}.json`, this.layer.source.networkOptions)
.then((hierarchy) => {
this.numPoints = hierarchy[this.id];

const stack = [];
stack.push(this);

while (stack.length) {
const node = stack.shift();
const depth = node.depth + 1;
const x = node.x * 2;
const y = node.y * 2;
const z = node.z * 2;

node.findAndCreateChild(depth, x, y, z, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y, z, hierarchy, stack);
node.findAndCreateChild(depth, x, y + 1, z, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y + 1, z, hierarchy, stack);
node.findAndCreateChild(depth, x, y, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x, y + 1, z + 1, hierarchy, stack);
node.findAndCreateChild(depth, x + 1, y + 1, z + 1, hierarchy, stack);
}
});
}

findAndCreateChild(depth, x, y, z, hierarchy, stack) {
Expand Down
11 changes: 7 additions & 4 deletions src/Core/PointCloudNode.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as THREE from 'three';
import { OBB } from 'ThreeExtended/math/OBB';

class PointCloudNode extends THREE.EventDispatcher {
constructor(numPoints = 0, layer) {
Expand All @@ -9,13 +10,15 @@ class PointCloudNode extends THREE.EventDispatcher {

this.children = [];
this.bbox = new THREE.Box3();
this.obb = new OBB();
this.sse = -1;
}

add(node, indexChild) {
this.children.push(node);
node.parent = this;
this.createChildAABB(node, indexChild);
add(childNode, indexChild) {
this.children.push(childNode);
childNode.parent = this;
this.createChildAABB(childNode, indexChild);
this.createChildOBB(childNode);
}

load() {
Expand Down
99 changes: 76 additions & 23 deletions src/Layer/EntwinePointTileLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import EntwinePointTileNode from 'Core/EntwinePointTileNode';
import PointCloudLayer from 'Layer/PointCloudLayer';
import Extent from 'Core/Geographic/Extent';
import Coordinates from 'Core/Geographic/Coordinates';

const bboxMesh = new THREE.Mesh();
const box3 = new THREE.Box3();
bboxMesh.geometry.boundingBox = box3;
import proj4 from 'proj4';

/**
* @property {boolean} isEntwinePointTileLayer - Used to checkout whether this
Expand Down Expand Up @@ -55,34 +52,90 @@ class EntwinePointTileLayer extends PointCloudLayer {
if (this.crs !== config.crs) { console.warn('layer.crs is different from View.crs'); }
this.root = new EntwinePointTileNode(0, 0, 0, 0, this, -1);

const coord = new Coordinates(this.source.crs || config.crs, 0, 0, 0);
const coordBoundsMin = new Coordinates(crs, 0, 0, 0);
const coordBoundsMax = new Coordinates(crs, 0, 0, 0);
coord.setFromValues(
this.source.boundsConforming[0],
this.source.boundsConforming[1],
this.source.boundsConforming[2],
);
coord.as(crs, coordBoundsMin);
coord.setFromValues(
this.source.boundsConforming[3],
this.source.boundsConforming[4],
this.source.boundsConforming[5],
);
coord.as(crs, coordBoundsMax);

this.root.bbox.setFromPoints([coordBoundsMin.toVector3(), coordBoundsMax.toVector3()]);
let forward = (x => x);
if (this.source.crs !== this.crs) {
try {
forward = proj4(this.source.crs, this.crs).forward;
} catch (err) {
throw new Error(`${err} is not defined in proj4`);
}
}

// for BBOX
const boundsConforming = [
...forward(this.source.boundsConforming.slice(0, 3)),
...forward(this.source.boundsConforming.slice(3, 6)),
];
this.clamp = {
zmin: boundsConforming[2],
zmax: boundsConforming[5],
};


this.minElevationRange = this.source.boundsConforming[2];
this.maxElevationRange = this.source.boundsConforming[5];

const bounds = [
...forward(this.source.bounds.slice(0, 3)),
...forward(this.source.bounds.slice(3, 6)),
];

this.root.bbox.setFromArray(bounds);
this.extent = Extent.fromBox3(crs, this.root.bbox);

const centerZ0 = this.source.boundsConforming
.slice(0, 2)
.map((val, i) => Math.floor((val + this.source.boundsConforming[i + 3]) * 0.5));
centerZ0.push(0);

const geometry = new THREE.BufferGeometry();
const points = new THREE.Points(geometry);

const matrixWorld = new THREE.Matrix4();
const matrixWorldInverse = new THREE.Matrix4();

let origin = new Coordinates(this.crs);
if (this.crs === 'EPSG:4978') {
const axisZ = new THREE.Vector3(0, 0, 1);
const alignYtoEast = new THREE.Quaternion();
const center = new Coordinates(this.source.crs, centerZ0);
origin = center.as('EPSG:4978');
const center4326 = origin.as('EPSG:4326');

// align Z axe to geodesic normal.
points.quaternion.setFromUnitVectors(axisZ, origin.geodesicNormal);
// align Y axe to East
alignYtoEast.setFromAxisAngle(axisZ, THREE.MathUtils.degToRad(90 + center4326.longitude));
points.quaternion.multiply(alignYtoEast);
}
points.updateMatrixWorld();

matrixWorld.copy(points.matrixWorld);
matrixWorldInverse.copy(matrixWorld).invert();

// proj in repere local (apply rotation) to get obb from bbox
const boundsLocal = [];
for (let i = 0; i < bounds.length; i += 3) {
const coord = new THREE.Vector3(...bounds.slice(i, i + 3)).sub(origin.toVector3());
const coordlocal = coord.applyMatrix4(matrixWorldInverse);
boundsLocal.push(...coordlocal);
}

const positionsArray = new Float32Array(boundsLocal);
const positionBuffer = new THREE.BufferAttribute(positionsArray, 3);
geometry.setAttribute('position', positionBuffer);

geometry.computeBoundingBox();

this.root.obb.fromBox3(geometry.boundingBox);
this.root.obb.applyMatrix4(matrixWorld);
this.root.obb.position = origin.toVector3();

// NOTE: this spacing is kinda arbitrary here, we take the width and
// length (height can be ignored), and we divide by the specified
// span in ept.json. This needs improvements.
this.spacing = (Math.abs(coordBoundsMax.x - coordBoundsMin.x)
+ Math.abs(coordBoundsMax.y - coordBoundsMin.y)) / (2 * this.source.span);
this.spacing = (Math.abs(this.source.bounds[3] - this.source.bounds[0])
+ Math.abs(this.source.bounds[4] - this.source.bounds[1])) / (2 * this.source.span);

return this.root.loadOctree().then(resolve);
});
Expand Down
Loading

0 comments on commit 52b0639

Please sign in to comment.