Skip to content

Commit

Permalink
refactor(EntwinePointCloud): Multisource
Browse files Browse the repository at this point in the history
  • Loading branch information
ftoromanoff committed Sep 6, 2024
1 parent e4d7ca1 commit 4aa157e
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 123 deletions.
10 changes: 8 additions & 2 deletions examples/entwine_3d_loader.html
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,17 @@

function onLayerReady() {
var lookAt = new itowns.THREE.Vector3();
eptLayer.root.bbox.getCenter(lookAt);

const bboxes = eptLayer.root.map(root => root.bbox);
let bbox = bboxes[0];
for (let i = 1; i < bboxes.length; i++) {
bbox = bbox.union(bboxes[i]);
}
bbox.getCenter(lookAt);
var coordLookAt = new itowns.Coordinates(view.referenceCrs, lookAt);

var size = new itowns.THREE.Vector3();
eptLayer.root.bbox.getSize(size);
bbox.getSize(size);

view.controls.lookAtCoordinate({
coord: coordLookAt,
Expand Down
14 changes: 10 additions & 4 deletions examples/entwine_simple_loader.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,19 @@
function onLayerReady() {
var lookAt = new itowns.THREE.Vector3();
var size = new itowns.THREE.Vector3();
eptLayer.root.bbox.getSize(size);
eptLayer.root.bbox.getCenter(lookAt);

const bboxes = eptLayer.root.map(root => root.bbox);
let bbox = bboxes[0];
for (let i = 1; i < bboxes.length; i++) {
bbox = bbox.union(bboxes[i]);
}
bbox.getSize(size);
bbox.getCenter(lookAt);

view.camera3D.far = 2.0 * size.length();

controls.groundLevel = eptLayer.root.bbox.min.z;
var position = eptLayer.root.bbox.max.clone().add(
controls.groundLevel = bbox.min.z;
var position = bbox.max.clone().add(
size.multiply({ x: 0, y: 0, z: size.x / size.z })
);

Expand Down
10 changes: 6 additions & 4 deletions src/Core/EntwinePointTileNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,25 @@ class EntwinePointTileNode extends PointCloudNode {
* [Entwine
* documentation](https://entwine.io/entwine-point-tile.html#ept-data)
* @param {EntwinePointTileLayer} layer - The layer the node is attached to.
* @param {EntwinePointTileSource} source - The data source for the node.
* @param {number} [numPoints=0] - The number of points in this node. If
* `-1`, it means that the octree hierarchy associated to this node needs to
* be loaded.
*/
constructor(depth, x, y, z, layer, numPoints = 0) {
constructor(depth, x, y, z, layer, source, numPoints = 0) {
super(numPoints, layer);

this.isEntwinePointTileNode = true;

this.source = source;
this.depth = depth;
this.x = x;
this.y = y;
this.z = z;

this.id = buildId(depth, x, y, z);

this.url = `${this.layer.source.url}/ept-data/${this.id}.${this.layer.source.extension}`;
this.url = `${this.source.url}/ept-data/${this.id}.${this.source.extension}`;
}

createChildAABB(childNode) {
Expand Down Expand Up @@ -116,7 +118,7 @@ class EntwinePointTileNode extends PointCloudNode {
}

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

Expand Down Expand Up @@ -147,7 +149,7 @@ class EntwinePointTileNode extends PointCloudNode {
const numPoints = hierarchy[id];

if (typeof numPoints == 'number') {
const child = new EntwinePointTileNode(depth, x, y, z, this.layer, numPoints);
const child = new EntwinePointTileNode(depth, x, y, z, this.layer, this.source, numPoints);
this.add(child);
stack.push(child);
}
Expand Down
193 changes: 103 additions & 90 deletions src/Layer/EntwinePointTileLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,96 +47,109 @@ class EntwinePointTileLayer extends PointCloudLayer {
this.scale = new THREE.Vector3(1, 1, 1);

const resolve = this.addInitializationStep();
this.whenReady = this.source.whenReady.then(() => {
if (this.crs !== config.crs) { console.warn('layer.crs is different from View.crs'); }
this.root = new EntwinePointTileNode(0, 0, 0, 0, this, -1);

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);

// Get the transformation between the data coordinate syteme and the view's.
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(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);
});

const promises = [];
this.root = [];
this.whenReady = this.source.whenReady
.then((sources) => {
if (sources.isSource) { sources = [sources]; }
sources.forEach((source) => {
promises.push(source.whenReady
.then((source) => {
if (this.crs !== config.crs) { console.warn('layer.crs is different from View.crs'); }
const root = new EntwinePointTileNode(0, 0, 0, 0, this, source, -1);

let forward = (x => x);
if (source.crs !== this.crs) {
try {
forward = proj4(source.crs, this.crs).forward;
} catch (err) {
throw new Error(`${err} is not defined in proj4`);
}
}

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

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

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

root.bbox.setFromArray(bounds);

// Get the transformation between the data coordinate syteme and the view's.
const centerZ0 = source.boundsConforming
.slice(0, 2)
.map((val, i) => Math.floor((val + 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(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();

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

this.root.push(root);

// 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(source.bounds[3] - source.bounds[0])
+ Math.abs(source.bounds[4] - source.bounds[1])) / (2 * source.span);

return root.loadOctree().then(resolve);
}));
});
this.whenReady = Promise.all(promises);
});
}
}

Expand Down
52 changes: 29 additions & 23 deletions test/unit/entwine.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,12 @@ describe('Entwine Point Tile', function () {
it('pre updates and finds the root', () => {
const element = layer.preUpdate(context, new Set([layer]));
assert.strictEqual(element.length, 1);
assert.deepStrictEqual(element[0], layer.root);
assert.deepStrictEqual(element[0], layer.root[0]);
});

it('tries to update on the root and fails', function (done) {
layer.update(context, layer, layer.root);
layer.root.promise
layer.update(context, layer, layer.root[0]);
layer.root[0].promise
.then((res) => {
assert.ok(res instanceof Error);
done();
Expand All @@ -99,14 +99,19 @@ describe('Entwine Point Tile', function () {

it('tries to update on the root and succeeds', function (done) {
const lookAt = new Vector3();
const coord = new Coordinates(view.referenceCrs, layer.root.bbox.getCenter(lookAt));
const bboxes = layer.root.map(root => root.bbox);
let bbox = bboxes[0];
for (let i = 1; i < bboxes.length; i++) {
bbox = bbox.union(bboxes[i]);
}
const coord = new Coordinates(view.referenceCrs, bbox.getCenter(lookAt));
view.controls.lookAtCoordinate({
coord,
range: 250,
}, false)
.then(() => {
layer.update(context, layer, layer.root);
layer.root.promise
layer.update(context, layer, layer.root[0]);
layer.root[0].promise
.then(() => {
done();
});
Expand All @@ -121,27 +126,28 @@ describe('Entwine Point Tile', function () {
describe('Entwine Point Tile Node', function () {
let root;
before(function () {
const layer = { source: { url: 'http://server.geo', extension: 'laz' } };
root = new EntwinePointTileNode(0, 0, 0, 0, layer, 4000);
const source = { url: 'http://server.geo', extension: 'laz' };
const layer = { source };
root = new EntwinePointTileNode(0, 0, 0, 0, layer, source, 4000);
root.bbox.setFromArray([1000, 1000, 1000, 0, 0, 0]);
root.obb.fromBox3(root.bbox);
root.obb.position = root.obb.center;

root.add(new EntwinePointTileNode(1, 0, 0, 0, layer, 3000));
root.add(new EntwinePointTileNode(1, 0, 0, 1, layer, 3000));
root.add(new EntwinePointTileNode(1, 0, 1, 1, layer, 3000));

root.children[0].add(new EntwinePointTileNode(2, 0, 0, 0, layer, 2000));
root.children[0].add(new EntwinePointTileNode(2, 0, 1, 0, layer, 2000));
root.children[1].add(new EntwinePointTileNode(2, 0, 1, 3, layer, 2000));
root.children[2].add(new EntwinePointTileNode(2, 0, 2, 2, layer, 2000));
root.children[2].add(new EntwinePointTileNode(2, 0, 3, 3, layer, 2000));

root.children[0].children[0].add(new EntwinePointTileNode(3, 0, 0, 0, layer, 1000));
root.children[0].children[0].add(new EntwinePointTileNode(3, 0, 1, 0, layer, 1000));
root.children[1].children[0].add(new EntwinePointTileNode(3, 0, 2, 7, layer, 1000));
root.children[2].children[0].add(new EntwinePointTileNode(3, 0, 5, 4, layer, 1000));
root.children[2].children[1].add(new EntwinePointTileNode(3, 1, 6, 7, layer));
root.add(new EntwinePointTileNode(1, 0, 0, 0, layer, source, 3000));
root.add(new EntwinePointTileNode(1, 0, 0, 1, layer, source, 3000));
root.add(new EntwinePointTileNode(1, 0, 1, 1, layer, source, 3000));

root.children[0].add(new EntwinePointTileNode(2, 0, 0, 0, layer, source, 2000));
root.children[0].add(new EntwinePointTileNode(2, 0, 1, 0, layer, source, 2000));
root.children[1].add(new EntwinePointTileNode(2, 0, 1, 3, layer, source, 2000));
root.children[2].add(new EntwinePointTileNode(2, 0, 2, 2, layer, source, 2000));
root.children[2].add(new EntwinePointTileNode(2, 0, 3, 3, layer, source, 2000));

root.children[0].children[0].add(new EntwinePointTileNode(3, 0, 0, 0, layer, source, 1000));
root.children[0].children[0].add(new EntwinePointTileNode(3, 0, 1, 0, layer, source, 1000));
root.children[1].children[0].add(new EntwinePointTileNode(3, 0, 2, 7, layer, source, 1000));
root.children[2].children[0].add(new EntwinePointTileNode(3, 0, 5, 4, layer, source, 1000));
root.children[2].children[1].add(new EntwinePointTileNode(3, 1, 6, 7, layer, source));
});

it('finds the common ancestor of two nodes', () => {
Expand Down

0 comments on commit 4aa157e

Please sign in to comment.