Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Entwine a la volee #2272

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
],
};
6 changes: 4 additions & 2 deletions examples/entwine_3d_loader.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
}

function readEPTURL() {
var url = document.getElementById('ept_url').value || new URL(location.href).searchParams.get('ept');
const urlParams = new URL(location.href).searchParams
var url = document.getElementById('ept_url').value || urlParams.get('ept');

if (url) {
loadEPT(url);
Expand All @@ -98,7 +99,8 @@

itowns.View.prototype.addLayer.call(view, eptLayer).then(onLayerReady);

debug.PointCloudDebug.initTools(view, eptLayer, debugGui);
eptLayer.whenReady
.then(() => debug.PointCloudDebug.initTools(view, eptLayer, debugGui));
}

readEPTURL();
Expand Down
8 changes: 5 additions & 3 deletions examples/entwine_simple_loader.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
var debugGui = new dat.GUI();
var viewerDiv = document.getElementById('viewerDiv');
viewerDiv.style.display = 'block';
var view = new itowns.View('EPSG:3946', viewerDiv);
var view = new itowns.View('EPSG:3857', viewerDiv);
// put 'EPSG:3857' for ept=https://s3-us-west-2.amazonaws.com/usgs-lidar-public/AK_BrooksCamp_2012 if you don't want reprojection
// put 'EPSG:3946' for ept=https://download.data.grandlyon.com/files/grandlyon/imagerie/mnt2018/lidar/ept if you don't want reprojection
view.mainLoop.gfxEngine.renderer.setClearColor(0xcccccc);

const controls = new itowns.PlanarControls(view);
Expand All @@ -52,7 +54,7 @@
view.camera3D.far = 2.0 * size.length();

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

Expand Down Expand Up @@ -88,7 +90,7 @@
eptSource = new itowns.EntwinePointTileSource({ url });

if (eptLayer) {
debugGUI.removeFolder(eptLayer.debugUI);
debugGui.removeFolder(eptLayer.debugUI);
view.removeLayer('Entwine Point Tile');
view.notifyChange();
eptLayer.delete();
Expand Down
12 changes: 11 additions & 1 deletion src/Core/CopcNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class CopcNode extends PointCloudNode {
}

/**
* Create an (A)xis (A)ligned (B)ounding (B)ox for the given node given
* Create an (A)xis (A)ligned (B)ounding (B)ox for the node given
* `this` is its parent.
* @param {CopcNode} node - The child node
*/
Expand Down Expand Up @@ -90,6 +90,16 @@ class CopcNode extends PointCloudNode {
node.bbox.max.copy(node.bbox.min).add(size);
}

/**
* Create an (O)riented (B)ounding (B)ox for the node given
* `this` is its parent.
* @param {CopcNode} node - The child node
*/
createChildOBB(node) {
// to improve
node.obb.fromBox3(node.bbox);
}

/**
* Create a CopcNode from the provided subtree and add it as child
* of the current node.
Expand Down
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
12 changes: 9 additions & 3 deletions src/Core/Geographic/Crs.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function formatToEPSG(crs) {
const UNIT = {
DEGREE: 1,
METER: 2,
FOOT: 3,
};

function is4326(crs) {
Expand All @@ -48,20 +49,25 @@ function isGeocentric(crs) {
function _unitFromProj4Unit(projunit) {
if (projunit === 'degrees') {
return UNIT.DEGREE;
} else if (projunit === 'm') {
} else if (projunit === 'm' || projunit === 'meter') {
return UNIT.METER;
} else if (projunit === 'foot') {
return UNIT.FOOT;
} else {
return undefined;
}
}

function toUnit(crs) {
mustBeString(crs);
if (crs.startsWith('NAD')) {
return _unitFromProj4Unit(proj4.defs(crs).units);
}
switch (crs) {
case 'EPSG:4326' : return UNIT.DEGREE;
case 'EPSG:4978' : return UNIT.METER;
default: {
const p = proj4.defs(formatToEPSG(crs));
const p = proj4.defs(crs.startsWith('NAD') ? crs : formatToEPSG(crs));
if (!p) {
return undefined;
}
Expand Down Expand Up @@ -129,7 +135,7 @@ export default {
* Get the unit to use with the CRS.
*
* @param {string} crs - The CRS to get the unit from.
* @return {number} Either `UNIT.METER`, `UNIT.DEGREE` or `undefined`.
* @return {number} Either `UNIT.METER`, `UNIT.DEGREE`, `UNIT.FOOT` or `undefined`.
*/
toUnit,

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, indexChild);
}

load() {
Expand Down
5 changes: 5 additions & 0 deletions src/Core/PotreeNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ class PotreeNode extends PointCloudNode {
}
}

createChildOBB(node) {
// to check if it's enought
node.obb.fromBox3(node.bbox);
}

get octreeIsLoaded() {
return !(this.childrenBitField && this.children.length === 0);
}
Expand Down
95 changes: 85 additions & 10 deletions src/Layer/EntwinePointTileLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import * as THREE from 'three';
import EntwinePointTileNode from 'Core/EntwinePointTileNode';
import PointCloudLayer from 'Layer/PointCloudLayer';
import Extent from 'Core/Geographic/Extent';
import Coordinates from 'Core/Geographic/Coordinates';
import proj4 from 'proj4';

const bboxMesh = new THREE.Mesh();
const box3 = new THREE.Box3();
bboxMesh.geometry.boundingBox = box3;
// const bboxMesh = new THREE.Mesh();
// const box3 = new THREE.Box3();
// bboxMesh.geometry.boundingBox = box3;

/**
* @property {boolean} isEntwinePointTileLayer - Used to checkout whether this
Expand Down Expand Up @@ -51,20 +53,93 @@ class EntwinePointTileLayer extends PointCloudLayer {

const resolve = this.addInitializationStep();
this.whenReady = this.source.whenReady.then(() => {
const crs = this.crs || 'EPSG:4326';
if (this.crs !== config.crs) { console.warn('layer.crs is different from View.crs'); }
this.root = new EntwinePointTileNode(0, 0, 0, 0, this, -1);
this.root.bbox.min.fromArray(this.source.boundsConforming, 0);
this.root.bbox.max.fromArray(this.source.boundsConforming, 3);

const forward = (this.source.crs !== this.crs) ?
proj4(this.source.crs, this.crs).forward :
(x => x);

// 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];

this.extent = Extent.fromBox3(config.crs || 'EPSG:4326', this.root.bbox);
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(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);
});
}

get spacing() {
return this.source.spacing;
}
}

export default EntwinePointTileLayer;
Loading
Loading