Skip to content

Commit

Permalink
perf(3dtiles): Transform 3d tiles region bounding volumes to spheres
Browse files Browse the repository at this point in the history
fix(3dtiles): Fix a bug in 3d tiles bounding spheres subdivisions
fix(3dtiles): Fix 3D tiles debug bounding spheres

BREAKING CHANGE: Remove region, box and sphere properties of C3DTBoundingVolume.
They have been replaced by volume property which contains a THREE.Box3 (for
box) or a THREE.Sphere (for sphere or region). Initial bounding volume type
can be retrieved with the initialVolumeType property.
  • Loading branch information
jailln committed Nov 27, 2023
1 parent 1d10290 commit f0eaf96
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 212 deletions.
2 changes: 1 addition & 1 deletion src/Core/3DTiles/C3DTBatchTable.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import utf8Decoder from 'Utils/Utf8Decoder';
import binaryPropertyAccessor from './utils/BinaryPropertyAccessor';
import C3DTilesTypes from './C3DTilesTypes';
import { C3DTilesTypes } from './C3DTilesEnums';

/** @classdesc
* A 3D Tiles
Expand Down
198 changes: 123 additions & 75 deletions src/Core/3DTiles/C3DTBoundingVolume.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,122 @@
import * as THREE from 'three';
import Extent from '../Geographic/Extent';
import OBB from '../../Renderer/OBB';
import C3DTilesTypes from './C3DTilesTypes';

const matrix = new THREE.Matrix4();
const center = new THREE.Vector3();
const size = new THREE.Vector3();
const extent = new Extent('EPSG:4326', 0, 0, 0, 0);
const sphereCenter = new THREE.Vector3();
import Ellipsoid from 'Core/Math/Ellipsoid';
import Coordinates from '../Geographic/Coordinates';
import { C3DTilesTypes, C3DTilesBoundingVolumeTypes } from './C3DTilesEnums';

const ellipsoid = new Ellipsoid();

// bounding box scratch variables
const boxSize = new THREE.Vector3();
const boxCenter = new THREE.Vector3();

// Bounding region scratch variables
const southEastUpCarto = new Coordinates('EPSG:4326');
const southEastUpVec3 = new THREE.Vector3();
const northWestBottomCarto = new Coordinates('EPSG:4326');
const northWestBottomVec3 = new THREE.Vector3();
const radiusScratch = new THREE.Vector3();

// Culling scratch value
const worldCoordinateCenter = new THREE.Vector3();

/**
* Bounding region is converted to a bounding sphere to simplify and speed computation and culling. This function
* computes a sphere enclosing the bounding region.
* @param {Object} region - the parsed json from the tile representing the region
* @param {THREE.Matrix4} tileMatrixInverse - the inverse transformation matrix of the tile to transform the produced
* sphere from a global to a reference local to the tile
* @return {THREE.Sphere} a sphere enclosing the given region
*/
function initFromRegion(region, tileMatrixInverse) {
const east = region[2];
const west = region[0];
const south = region[1];
const north = region[3];
const minHeight = region[4];
const maxHeight = region[5];

const eastDeg = THREE.MathUtils.radToDeg(east);
const westDeg = THREE.MathUtils.radToDeg(west);
const southDeg = THREE.MathUtils.radToDeg(south);
const northDeg = THREE.MathUtils.radToDeg(north);

northWestBottomCarto.setFromValues(westDeg, northDeg, minHeight);
ellipsoid.cartographicToCartesian(northWestBottomCarto, northWestBottomVec3);

southEastUpCarto.setFromValues(eastDeg, southDeg, maxHeight);
ellipsoid.cartographicToCartesian(southEastUpCarto, southEastUpVec3);

const regionCenter = new THREE.Vector3();
regionCenter.lerpVectors(northWestBottomVec3, southEastUpVec3, 0.5);
const radius = radiusScratch.subVectors(northWestBottomVec3, southEastUpVec3).length() / 2;

const sphere = new THREE.Sphere(regionCenter, radius);
sphere.applyMatrix4(tileMatrixInverse);

return sphere;
}

/**
* Create a bounding box from a json describing a box in a 3D Tiles tile.
* @param {Object} box - the parsed json from the tile representing the box
* @return {THREE.Box3} the bounding box of the tile
*/
function initFromBox(box) {
// box[0], box[1], box[2] = center of the box
// box[3], box[4], box[5] = x axis direction and half-length
// box[6], box[7], box[8] = y axis direction and half-length
// box[9], box[10], box[11] = z axis direction and half-length
boxCenter.set(box[0], box[1], box[2]);
boxSize.set(box[3], box[7], box[11]).multiplyScalar(2);
const box3 = new THREE.Box3();
box3.setFromCenterAndSize(boxCenter, boxSize);
return box3;
}

/**
* Creats a bounding sphere from a json describing a sphere in a 3D Tiles tile.
* @param {Object} sphere - the parsed json from the tile representing the sphere
* @returns {THREE.Sphere} the bounding sphere of the tile
*/
function initFromSphere(sphere) {
const sphereCenter = new THREE.Vector3();
sphereCenter.set(sphere[0], sphere[1], sphere[2]);
return new THREE.Sphere(sphereCenter, sphere[3]);
}

/**
* @classdesc 3D Tiles
* [bounding volume](https://github.com/AnalyticalGraphicsInc/3d-tiles/blob/master/specification/schema/boundingVolume.schema.json)
* Used to represent bounding volumes and viewer request volumes. The input bounding volume (from the dataset) can be a
* box, a sphere or a region. Regions are transformed to spheres internally for simplification of parsing and to speed
* up computations such as culling.
* @property {C3DTilesTypes} type - Used by 3D Tiles extensions
* (e.g. {@link C3DTBatchTableHierarchyExtension}) to know in which context
* (i.e. for which 3D Tiles class) the parsing of the extension should be done.
* @property {THREE.Box3} box - Bounding box, defined only if the Bounding Volume
* is a box.
* @property {OBB} region - Bounding region, defined only if the Bounding
* Volume is a region.
* @property {THREE.Sphere} sphere - Bounding sphere, defined only if the
* Bounding Volume is a sphere.
* @property {String} initialVolumeType - the initial volume type to be able to dissociate spheres
* and regions if needed since both are converted to spheres (one of {@link C3DTilesBoundingVolumeTypes})
* @property {THREE.Box3|THREE.Sphere} volume - The 3D bounding volume created. Can be a THREE.Box3 for bounding volumes
* of types box or a THREE.Sphere for bounding volumes of type sphere or region.
* @property {object} extensions - 3D Tiles extensions of the bounding volume
* stored in the following format:
* {extensioName1: extensionObject1, extensioName2: extensionObject2, ...}
*/
class C3DTBoundingVolume {
constructor(json, inverseTileTransform, registeredExtensions) {
constructor(json, tileMatrixInverse, registeredExtensions) {
this.type = C3DTilesTypes.boundingVolume;

// Init bounding volume
if (json.region) {
this.initBoundingRegion(json.region, inverseTileTransform);
this.initialVolumeType = C3DTilesBoundingVolumeTypes.region;
this.volume = initFromRegion(json.region, tileMatrixInverse);
} else if (json.box) {
this.initBoundingBox(json.box);
this.initialVolumeType = C3DTilesBoundingVolumeTypes.box;
this.volume = initFromBox(json.box);
} else if (json.sphere) {
this.initBoundingSphere(json.sphere);
this.initialVolumeType = C3DTilesBoundingVolumeTypes.sphere;
this.volume = initFromSphere(json.sphere);
} else {
throw new Error('3D Tiles nodes must have a bounding volume');
throw new Error(`Unknown bounding volume type: ${json}. 3D Tiles nodes must have a bounding volume of type
region, box or sphere.`);
}

if (json.extensions) {
Expand All @@ -47,73 +125,43 @@ class C3DTBoundingVolume {
}
}

initBoundingRegion(region, inverseTileTransform) {
extent.set(THREE.MathUtils.radToDeg(region[0]),
THREE.MathUtils.radToDeg(region[2]),
THREE.MathUtils.radToDeg(region[1]),
THREE.MathUtils.radToDeg(region[3]));
const regionBox = new OBB();
regionBox.setFromExtent(extent);
regionBox.updateZ({ min: region[4], max: region[5] });
// at this point box.matrix = box.epsg4978_from_local, so
// we transform it in parent_from_local by using parent's
// epsg4978_from_local which from our point of view is
// epsg4978_from_parent. box.matrix = (epsg4978_from_parent ^ -1) *
// epsg4978_from_local = parent_from_epsg4978 * epsg4978_from_local =
// parent_from_local
regionBox.matrix.premultiply(inverseTileTransform);
// update position, rotation and scale
regionBox.matrix.decompose(regionBox.position, regionBox.quaternion, regionBox.scale);
this.region = regionBox;
}

initBoundingBox(box) {
// box[0], box[1], box[2] = center of the box
// box[3], box[4], box[5] = x axis direction and half-length
// box[6], box[7], box[8] = y axis direction and half-length
// box[9], box[10], box[11] = z axis direction and half-length
center.set(box[0], box[1], box[2]);
size.set(box[3], box[7], box[11]).multiplyScalar(2);
this.box = new THREE.Box3();
this.box.setFromCenterAndSize(center, size);
}

initBoundingSphere(sphere) {
sphereCenter.set(sphere[0], sphere[1], sphere[2]);
this.sphere = new THREE.Sphere(sphereCenter, sphere[3]);
}

/**
* Performs camera frustum culling on bounding volumes.
* @param {Camera} camera - the camera to perform culling for
* @param {THREE.Matrix4} tileMatrixWorld - the world matrix of the tile
* @returns {boolean} true if the tile should be culled out (bounding volume not in camera frustum), false otherwise.
*/
boundingVolumeCulling(camera, tileMatrixWorld) {
if (this.region &&
!camera.isBox3Visible(this.region.box3D,
matrix.multiplyMatrices(tileMatrixWorld, this.region.matrix))) {
return true;
}
if (this.box && !camera.isBox3Visible(this.box,
tileMatrixWorld)) {
return true;
if (this.initialVolumeType === C3DTilesBoundingVolumeTypes.box) {
return !camera.isBox3Visible(this.volume, tileMatrixWorld);
} else if (this.initialVolumeType === C3DTilesBoundingVolumeTypes.sphere ||
this.initialVolumeType === C3DTilesBoundingVolumeTypes.region) {
return !camera.isSphereVisible(this.volume, tileMatrixWorld);
} else {
throw new Error('Unknown bounding volume type.');
}
return this.sphere &&
!camera.isSphereVisible(this.sphere, tileMatrixWorld);
}

/**
* Checks if the camera is inside the [viewer request volumes](@link https://github.com/CesiumGS/3d-tiles/tree/main/specification#viewer-request-volume).
* @param {Camera} camera - the camera to perform culling for
* @param {THREE.Matrix4} tileMatrixWorld - the world matrix of the tile
* @returns {boolean} true if the camera is outside the viewer request volume, false otherwise.
*/
viewerRequestVolumeCulling(camera, tileMatrixWorld) {
if (this.region) {
if (this.initialVolumeType === C3DTilesBoundingVolumeTypes.region) {
console.warn('Region viewerRequestVolume not yet supported');
return true;
}
if (this.box) {
if (this.initialVolumeType === C3DTilesBoundingVolumeTypes.box) {
console.warn('Bounding box viewerRequestVolume not yet supported');
return true;
}
if (this.sphere) {
worldCoordinateCenter.copy(this.sphere.center);
if (this.initialVolumeType === C3DTilesBoundingVolumeTypes.sphere) {
worldCoordinateCenter.copy(this.volume.center);
worldCoordinateCenter.applyMatrix4(tileMatrixWorld);
// To check the distance between the center sphere and the camera
if (!(camera.camera3D.position.distanceTo(worldCoordinateCenter) <=
this.sphere.radius)) {
return true;
}
return !(camera.camera3D.position.distanceTo(worldCoordinateCenter) <= this.volume.radius);
}
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
* @property {String} batchtable - value: 'batchtable'
* @property {String} boundingVolume - value: 'bounding volume'
*/
const C3DTilesTypes = {
export const C3DTilesTypes = {
tileset: 'tileset',
batchtable: 'batchtable',
boundingVolume: 'boundingVolume',
};

export default C3DTilesTypes;
export const C3DTilesBoundingVolumeTypes = {
region: 'region',
box: 'box',
sphere: 'sphere',
};
21 changes: 11 additions & 10 deletions src/Core/3DTiles/C3DTileset.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import * as THREE from 'three';
import C3DTBoundingVolume from './C3DTBoundingVolume';
import C3DTilesTypes from './C3DTilesTypes';
import { C3DTilesTypes } from './C3DTilesEnums';

const inverseTileTransform = new THREE.Matrix4();
// Inverse transform of a tile, computed from the tile transform and used when parsing the bounding volume of a tile
// if the bounding volume is a region (https://github.com/CesiumGS/3d-tiles/tree/main/specification#region) which is
// in global coordinates and other bounding volumes are not. To harmonize, we transform back the bounding volume region
// to a reference local to the tile.
const tileMatrixInverse = new THREE.Matrix4();

/** @classdesc
* A 3D Tiles
Expand Down Expand Up @@ -74,23 +78,20 @@ class C3DTileset {
}
}

// inverseTileTransform is only used for volume.region
// tileMatrixInverse is only used for volume.region
if ((tile.viewerRequestVolume && tile.viewerRequestVolume.region)
|| (tile.boundingVolume && tile.boundingVolume.region)) {
if (tile._worldFromLocalTransform) {
inverseTileTransform.copy(tile._worldFromLocalTransform).invert();
tileMatrixInverse.copy(tile._worldFromLocalTransform).invert();
} else {
inverseTileTransform.identity();
tileMatrixInverse.identity();
}
}

tile.viewerRequestVolume = tile.viewerRequestVolume ?
new C3DTBoundingVolume(tile.viewerRequestVolume,
inverseTileTransform,
registeredExtensions) : undefined;
new C3DTBoundingVolume(tile.viewerRequestVolume, tileMatrixInverse, registeredExtensions) : null;
tile.boundingVolume = tile.boundingVolume ?
new C3DTBoundingVolume(tile.boundingVolume,
inverseTileTransform, registeredExtensions) : undefined;
new C3DTBoundingVolume(tile.boundingVolume, tileMatrixInverse, registeredExtensions) : null;

this.tiles.push(tile);
tile.tileId = this.tiles.length - 1;
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Geographic/Coordinates.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class Coordinates {
* You can find most projections and their proj4 code at [epsg.io]{@link https://epsg.io/}
* @param {number|Array<number>|Coordinates|THREE.Vector3} [v0=0] -
* x or longitude value, or a more complex one: it can be an array of three
* numbers, being x/lon, x/lat, z/alt, or it can be `THREE.Vector3`. It can
* numbers, being x/lon, y/lat, z/alt, or it can be `THREE.Vector3`. It can
* also simply be a Coordinates.
* @param {number} [v1=0] - y or latitude value.
* @param {number} [v2=0] - z or altitude value.
Expand Down
2 changes: 1 addition & 1 deletion src/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,6 @@ export { default as C3DTileset } from './Core/3DTiles/C3DTileset';
export { default as C3DTBoundingVolume } from './Core/3DTiles/C3DTBoundingVolume';
export { default as C3DTBatchTable } from './Core/3DTiles/C3DTBatchTable';
export { default as C3DTExtensions } from './Core/3DTiles/C3DTExtensions';
export { default as C3DTilesTypes } from './Core/3DTiles/C3DTilesTypes';
export { C3DTilesTypes, C3DTilesBoundingVolumeTypes } from './Core/3DTiles/C3DTilesEnums';
export { default as C3DTBatchTableHierarchyExtension } from './Core/3DTiles/C3DTBatchTableHierarchyExtension';
export { process3dTilesNode, $3dTilesCulling, $3dTilesSubdivisionControl } from 'Process/3dTilesProcessing';
Loading

0 comments on commit f0eaf96

Please sign in to comment.