Remove abstract class for bounding volumes, and always treat

bounding volumes as OBB

Greatly simplifies the code. There's a small loss of efficiency
since we will always be expanding out spheres to their bounding
boxes, but given we always require boxes for QGIS 3d nodes
anyway this will only impact 2d rendering.
This commit is contained in:
Nyall Dawson 2023-08-10 07:37:55 +10:00 committed by Martin Dobias
parent 1c265af5e4
commit 0e363e66c2
23 changed files with 248 additions and 853 deletions

View File

@ -40,6 +40,11 @@ Parses a ``box`` object from a Cesium JSON document to an oriented bounding box.
static QgsSphere parseSphere( const QVariantList &sphere );
%Docstring
Parses a ``sphere`` object from a Cesium JSON document.
%End
static QgsSphere transformSphere( const QgsSphere &sphere, const QgsMatrix4x4 &transform );
%Docstring
Applies a ``transform`` to a sphere.
%End
static QByteArray extractGltfFromB3dm( const QByteArray &tileContent );

View File

@ -10,10 +10,10 @@
class QgsAbstractTiledSceneBoundingVolume
class QgsTiledSceneBoundingVolume
{
%Docstring(signature="appended")
Abstract base class for bounding volumes for tiled scene nodes.
Represents a bounding volume for a tiled scene.
.. versionadded:: 3.34
%End
@ -23,32 +23,12 @@ Abstract base class for bounding volumes for tiled scene nodes.
%End
public:
%ConvertToSubClassCode
switch ( sipCpp->type() )
{
case Qgis::TiledSceneBoundingVolumeType::Region:
sipType = sipType_QgsTiledSceneBoundingVolumeRegion;
break;
case Qgis::TiledSceneBoundingVolumeType::OrientedBox:
sipType = sipType_QgsTiledSceneBoundingVolumeBox;
break;
case Qgis::TiledSceneBoundingVolumeType::Sphere:
sipType = sipType_QgsTiledSceneBoundingVolumeSphere;
break;
default:
sipType = 0;
break;
};
%End
virtual ~QgsAbstractTiledSceneBoundingVolume();
virtual Qgis::TiledSceneBoundingVolumeType type() const = 0;
QgsTiledSceneBoundingVolume( const QgsOrientedBox3D &box = QgsOrientedBox3D() );
%Docstring
Returns the type of the volume;
Constructor for QgsTiledSceneBoundingVolume, with the specified oriented ``box``.
%End
virtual QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const = 0;
QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const;
%Docstring
Returns the axis aligned bounding box of the volume.
@ -56,12 +36,7 @@ The optional ``transform`` and ``direction`` arguments should be used whenever t
to be transformed into a specific destination CRS, in order to correctly handle 3D coordinate transforms.
%End
virtual QgsAbstractTiledSceneBoundingVolume *clone() const = 0 /Factory/;
%Docstring
Returns a clone of the volume.
%End
virtual QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const = 0 /Factory/;
QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const /Factory/;
%Docstring
Returns a new geometry representing the 2-dimensional X/Y center slice of the volume.
@ -71,92 +46,16 @@ The optional ``transform`` and ``direction`` arguments should be used whenever t
to be transformed into a specific destination CRS, in order to correctly handle 3D coordinate transforms.
%End
virtual void transform( const QgsMatrix4x4 &transform ) = 0;
void transform( const QgsMatrix4x4 &transform );
%Docstring
Applies a ``transform`` to the bounding volume.
The actual result of transforming a bounding volume depends on subclass specific logic. For instance:
- transforming a :py:class:`QgsTiledSceneBoundingVolumeRegion` results in no change to the region
- transforming a :py:class:`QgsTiledSceneBoundingVolumeSphere` causes the radius to be multiplied by the maximum length of the transform scales
%End
virtual bool intersects( const QgsOrientedBox3D &box ) const = 0;
bool intersects( const QgsOrientedBox3D &box ) const;
%Docstring
Returns ``True`` if this bounds intersects the specified ``box``.
%End
};
class QgsTiledSceneBoundingVolumeRegion : QgsAbstractTiledSceneBoundingVolume
{
%Docstring(signature="appended")
A region bounding volume for tiled scene nodes.
.. versionadded:: 3.34
%End
%TypeHeaderCode
#include "qgstiledsceneboundingvolume.h"
%End
public:
QgsTiledSceneBoundingVolumeRegion( const QgsBox3D &region );
%Docstring
Constructor for QgsTiledSceneBoundingVolumeRegion, with the specified ``region``.
%End
virtual Qgis::TiledSceneBoundingVolumeType type() const ${SIP_FINAL};
virtual void transform( const QgsMatrix4x4 &transform ) ${SIP_FINAL};
virtual QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const ${SIP_FINAL} throw( QgsCsException );
virtual QgsTiledSceneBoundingVolumeRegion *clone() const ${SIP_FINAL} /Factory/;
virtual QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const ${SIP_FINAL} throw( QgsCsException ) /Factory/;
virtual bool intersects( const QgsOrientedBox3D &box ) const ${SIP_FINAL};
QgsBox3D region() const;
%Docstring
Returns the volume's region.
%End
};
class QgsTiledSceneBoundingVolumeBox : QgsAbstractTiledSceneBoundingVolume
{
%Docstring(signature="appended")
A oriented box bounding volume for tiled scene nodes.
.. versionadded:: 3.34
%End
%TypeHeaderCode
#include "qgstiledsceneboundingvolume.h"
%End
public:
QgsTiledSceneBoundingVolumeBox( const QgsOrientedBox3D &box );
%Docstring
Constructor for QgsTiledSceneBoundingVolumeBox, with the specified oriented ``box``.
%End
virtual Qgis::TiledSceneBoundingVolumeType type() const ${SIP_FINAL};
virtual void transform( const QgsMatrix4x4 &transform ) ${SIP_FINAL};
virtual QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const ${SIP_FINAL} throw( QgsCsException );
virtual QgsTiledSceneBoundingVolumeBox *clone() const ${SIP_FINAL} /Factory/;
virtual QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const ${SIP_FINAL} throw( QgsCsException ) /Factory/;
virtual bool intersects( const QgsOrientedBox3D &box ) const ${SIP_FINAL};
QgsOrientedBox3D box() const;
%Docstring
Returns the volume's oriented box.
@ -164,45 +63,6 @@ Returns the volume's oriented box.
};
class QgsTiledSceneBoundingVolumeSphere: QgsAbstractTiledSceneBoundingVolume
{
%Docstring(signature="appended")
A spherical bounding volume for tiled scene nodes.
.. versionadded:: 3.34
%End
%TypeHeaderCode
#include "qgstiledsceneboundingvolume.h"
%End
public:
QgsTiledSceneBoundingVolumeSphere( const QgsSphere &sphere );
%Docstring
Constructor for QgsTiledSceneBoundingVolumeSphere, with the specified ``sphere``.
%End
virtual Qgis::TiledSceneBoundingVolumeType type() const ${SIP_FINAL};
virtual void transform( const QgsMatrix4x4 &transform ) ${SIP_FINAL};
virtual QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const ${SIP_FINAL} throw( QgsCsException );
virtual QgsTiledSceneBoundingVolumeSphere *clone() const ${SIP_FINAL} /Factory/;
virtual QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const ${SIP_FINAL} throw( QgsCsException ) /Factory/;
virtual bool intersects( const QgsOrientedBox3D &box ) const ${SIP_FINAL};
QgsSphere sphere() const;
%Docstring
Returns the volume's sphere.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *

View File

@ -68,7 +68,7 @@ for the data provider for 2D use.
transforming bounding volumes or geometries associated with the provider.
%End
virtual const QgsAbstractTiledSceneBoundingVolume *boundingVolume() const = 0;
virtual const QgsTiledSceneBoundingVolume &boundingVolume() const = 0;
%Docstring
Returns the bounding volume for the data provider.

View File

@ -42,16 +42,20 @@ Returns the flags which affect how tiles are fetched.
QgsOrientedBox3D filterBox() const;
%Docstring
Returns the box from which data will be taken, in the layer's CRS.
Returns the box from which data will be taken.
The CRS for the box can be retrieved by :py:func:`~QgsTiledSceneRequest.filterBoxCrs`.
If the returned box is null, then no filter box is set.
.. seealso:: :py:func:`filterBoxCrs`
.. seealso:: :py:func:`setFilterBox`
%End
void setFilterBox( const QgsOrientedBox3D &box );
%Docstring
Sets the ``box`` from which data will be taken, in the layer's CRS.
Sets the ``box`` from which data will be taken.
An null ``box`` removes the filter.

View File

@ -9,7 +9,6 @@
class QgsTiledSceneTile
{
%Docstring(signature="appended")
@ -74,16 +73,14 @@ content is handled when its higher resolution children are also included.
.. seealso:: :py:func:`refinementProcess`
%End
void setBoundingVolume( QgsAbstractTiledSceneBoundingVolume *volume /Transfer/ );
void setBoundingVolume( const QgsTiledSceneBoundingVolume &volume );
%Docstring
Sets the bounding ``volume`` for the tile.
Ownership of ``volume`` is transferred to the tile.
.. seealso:: :py:func:`boundingVolume`
%End
const QgsAbstractTiledSceneBoundingVolume *boundingVolume() const;
const QgsTiledSceneBoundingVolume &boundingVolume() const;
%Docstring
Returns the bounding volume for the tile.

View File

@ -37,27 +37,8 @@ static bool hasLargeBounds( const QgsTiledSceneTile &t )
if ( t.geometricError() > 1e6 )
return true;
switch ( t.boundingVolume()->type() )
{
case Qgis::TiledSceneBoundingVolumeType::Region:
{
// TODO: is region always lat/lon in degrees?
QgsBox3D region = static_cast<const QgsTiledSceneBoundingVolumeRegion *>( t.boundingVolume() )->region();
return region.width() > 15 || region.height() > 15;
}
case Qgis::TiledSceneBoundingVolumeType::OrientedBox:
{
QgsOrientedBox3D box = static_cast<const QgsTiledSceneBoundingVolumeBox *>( t.boundingVolume() )->box();
QgsVector3D size = box.size();
return size.x() > 1e5 || size.y() > 1e5 || size.z() > 1e5;
}
case Qgis::TiledSceneBoundingVolumeType::Sphere:
return static_cast<const QgsTiledSceneBoundingVolumeSphere *>( t.boundingVolume() )->sphere().diameter() > 1e5;
}
return false;
const QgsVector3D size = t.boundingVolume().box().size();
return size.x() > 1e5 || size.y() > 1e5 || size.z() > 1e5;
}
@ -165,7 +146,6 @@ QgsTiledSceneChunkLoaderFactory::QgsTiledSceneChunkLoaderFactory( const Qgs3DMap
: mMap( map ), mRelativePathBase( relativePathBase ), mIndex( index )
{
mBoundsTransform = QgsCoordinateTransform( QgsCoordinateReferenceSystem( "EPSG:4978" ), mMap.crs(), mMap.transformContext() );
mRegionTransform = QgsCoordinateTransform( QgsCoordinateReferenceSystem( "EPSG:4979" ), mMap.crs(), mMap.transformContext() );
}
QgsChunkLoader *QgsTiledSceneChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const
@ -195,9 +175,8 @@ QgsChunkNode *QgsTiledSceneChunkLoaderFactory::nodeForTile( const QgsTiledSceneT
}
else
{
bool isRegion = t.boundingVolume()->type() == Qgis::TiledSceneBoundingVolumeType::Region;
QgsBox3D box = t.boundingVolume()->bounds( isRegion ? mRegionTransform : mBoundsTransform );
QgsAABB aabb = aabbConvert( box, mMap.origin() );
const QgsBox3D box = t.boundingVolume().bounds( mBoundsTransform );
const QgsAABB aabb = aabbConvert( box, mMap.origin() );
return new QgsChunkNode( nodeId, aabb, t.geometricError() );
}
}
@ -239,29 +218,26 @@ QVector<QgsChunkNode *> QgsTiledSceneChunkLoaderFactory::createChildren( QgsChun
// if the tile is huge, let's try to see if our scene is actually inside
// (if not, let' skip this child altogether!)
// TODO: make OBB of our scene in ECEF rather than just using center of the scene?
if ( t.boundingVolume()->type() == Qgis::TiledSceneBoundingVolumeType::OrientedBox )
const QgsOrientedBox3D obb = t.boundingVolume().box();
const QgsPointXY c = mMap.extent().center();
const QgsVector3D cEcef = mBoundsTransform.transform( QgsVector3D( c.x(), c.y(), 0 ), Qgis::TransformDirection::Reverse );
const QgsVector3D ecef2 = cEcef - obb.center();
const double *half = obb.halfAxes();
// this is an approximate check anyway, no need for double precision matrix/vector
QMatrix4x4 rot(
half[0], half[3], half[6], 0,
half[1], half[4], half[7], 0,
half[2], half[5], half[8], 0,
0, 0, 0, 1 );
QVector3D aaa = rot.inverted().map( ecef2.toVector3D() );
if ( aaa.x() > 1 || aaa.y() > 1 || aaa.z() > 1 ||
aaa.x() < -1 || aaa.y() < -1 || aaa.z() < -1 )
{
QgsOrientedBox3D obb = static_cast<const QgsTiledSceneBoundingVolumeBox *>( t.boundingVolume() )->box();
QgsPointXY c = mMap.extent().center();
QgsVector3D cEcef = mBoundsTransform.transform( QgsVector3D( c.x(), c.y(), 0 ), Qgis::TransformDirection::Reverse );
QgsVector3D ecef2 = cEcef - obb.center();
const double *half = obb.halfAxes();
// this is an approximate check anyway, no need for double precision matrix/vector
QMatrix4x4 rot(
half[0], half[3], half[6], 0,
half[1], half[4], half[7], 0,
half[2], half[5], half[8], 0,
0, 0, 0, 1 );
QVector3D aaa = rot.inverted().map( ecef2.toVector3D() );
if ( aaa.x() > 1 || aaa.y() > 1 || aaa.z() > 1 ||
aaa.x() < -1 || aaa.y() < -1 || aaa.z() < -1 )
{
continue;
}
continue;
}
}

View File

@ -87,7 +87,6 @@ class QgsTiledSceneChunkLoaderFactory : public QgsChunkLoaderFactory
QString mRelativePathBase;
mutable QgsTiledSceneIndex mIndex;
QgsCoordinateTransform mBoundsTransform;
QgsCoordinateTransform mRegionTransform;
};

View File

@ -58,7 +58,8 @@ class QgsCesiumTiledSceneIndex final : public QgsAbstractTiledSceneIndex
const json &tileset,
const QString &rootPath,
const QString &authCfg,
const QgsHttpHeaders &headers );
const QgsHttpHeaders &headers,
const QgsCoordinateTransformContext &transformContext );
std::unique_ptr< QgsTiledSceneTile > tileFromJson( const json &node, const QgsTiledSceneTile *parent );
QgsTiledSceneNode *nodeFromJson( const json &node, QgsTiledSceneNode *parent );
@ -85,6 +86,7 @@ class QgsCesiumTiledSceneIndex final : public QgsAbstractTiledSceneIndex
};
mutable QRecursiveMutex mLock;
QgsCoordinateTransformContext mTransformContext;
QString mRootPath;
std::unique_ptr< QgsTiledSceneNode > mRootNode;
QMap< long long, QgsTiledSceneNode * > mNodeMap;
@ -107,7 +109,7 @@ class QgsCesiumTilesDataProviderSharedData
QgsCoordinateReferenceSystem mLayerCrs;
QgsCoordinateReferenceSystem mSceneCrs;
std::unique_ptr< QgsAbstractTiledSceneBoundingVolume > mBoundingVolume;
QgsTiledSceneBoundingVolume mBoundingVolume;
QgsRectangle mExtent;
nlohmann::json mTileset;
@ -126,8 +128,9 @@ class QgsCesiumTilesDataProviderSharedData
// QgsCesiumTiledSceneIndex
//
QgsCesiumTiledSceneIndex::QgsCesiumTiledSceneIndex( const json &tileset, const QString &rootPath, const QString &authCfg, const QgsHttpHeaders &headers )
: mRootPath( rootPath )
QgsCesiumTiledSceneIndex::QgsCesiumTiledSceneIndex( const json &tileset, const QString &rootPath, const QString &authCfg, const QgsHttpHeaders &headers, const QgsCoordinateTransformContext &transformContext )
: mTransformContext( transformContext )
, mRootPath( rootPath )
, mAuthCfg( authCfg )
, mHeaders( headers )
{
@ -159,13 +162,45 @@ std::unique_ptr< QgsTiledSceneTile > QgsCesiumTiledSceneIndex::tileFromJson( con
tile->setTransform( transform );
const auto &boundingVolume = json[ "boundingVolume" ];
std::unique_ptr< QgsAbstractTiledSceneBoundingVolume > volume;
QgsTiledSceneBoundingVolume volume;
if ( boundingVolume.contains( "region" ) )
{
const QgsBox3D rootRegion = QgsCesiumUtils::parseRegion( boundingVolume[ "region" ] );
QgsBox3D rootRegion = QgsCesiumUtils::parseRegion( boundingVolume[ "region" ] );
if ( !rootRegion.isNull() )
{
volume = std::make_unique< QgsTiledSceneBoundingVolumeRegion >( rootRegion );
// we need to transform regions from EPSG:4979 to EPSG:4978
QVector< QgsVector3D > corners = rootRegion.corners();
QVector< double > x;
x.reserve( 8 );
QVector< double > y;
y.reserve( 8 );
QVector< double > z;
z.reserve( 8 );
for ( int i = 0; i < 8; ++i )
{
const QgsVector3D &corner = corners[i];
x.append( corner.x() );
y.append( corner.y() );
z.append( corner.z() );
}
QgsCoordinateTransform ct( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) ), mTransformContext );
ct.setBallparkTransformsAreAppropriate( true );
try
{
ct.transformInPlace( x, y, z );
}
catch ( QgsCsException & )
{
QgsDebugError( QStringLiteral( "Cannot transform region bounding volume" ) );
}
const auto minMaxX = std::minmax_element( x.constBegin(), x.constEnd() );
const auto minMaxY = std::minmax_element( y.constBegin(), y.constEnd() );
const auto minMaxZ = std::minmax_element( z.constBegin(), z.constEnd() );
volume = QgsTiledSceneBoundingVolume( QgsOrientedBox3D::fromBox3D( QgsBox3D( *minMaxX.first, *minMaxY.first, *minMaxZ.first, *minMaxX.second, *minMaxY.second, *minMaxZ.second ) ) );
// note that matrix transforms are NOT applied to region bounding volumes!
}
}
else if ( boundingVolume.contains( "box" ) )
@ -173,15 +208,18 @@ std::unique_ptr< QgsTiledSceneTile > QgsCesiumTiledSceneIndex::tileFromJson( con
const QgsOrientedBox3D bbox = QgsCesiumUtils::parseBox( boundingVolume["box"] );
if ( !bbox.isNull() )
{
volume = std::make_unique< QgsTiledSceneBoundingVolumeBox >( bbox );
volume = QgsTiledSceneBoundingVolume( bbox );
if ( !transform.isIdentity() )
volume.transform( transform );
}
}
else if ( boundingVolume.contains( "sphere" ) )
{
const QgsSphere sphere = QgsCesiumUtils::parseSphere( boundingVolume["sphere"] );
QgsSphere sphere = QgsCesiumUtils::parseSphere( boundingVolume["sphere"] );
if ( !sphere.isNull() )
{
volume = std::make_unique< QgsTiledSceneBoundingVolumeSphere >( sphere );
sphere = QgsCesiumUtils::transformSphere( sphere, transform );
volume = QgsTiledSceneBoundingVolume( QgsOrientedBox3D::fromBox3D( sphere.boundingBox() ) );
}
}
else
@ -189,12 +227,7 @@ std::unique_ptr< QgsTiledSceneTile > QgsCesiumTiledSceneIndex::tileFromJson( con
QgsDebugError( QStringLiteral( "unsupported boundingVolume format" ) );
}
if ( volume )
{
if ( !transform.isIdentity() )
volume->transform( transform );
tile->setBoundingVolume( volume.release() );
}
tile->setBoundingVolume( volume );
if ( json.contains( "geometricError" ) )
tile->setGeometricError( json["geometricError"].get< double >() );
@ -334,7 +367,7 @@ QVector< long long > QgsCesiumTiledSceneIndex::getTiles( const QgsTiledSceneRequ
// check filter box first -- if the node doesn't intersect, then don't include the node and don't traverse
// to its children
if ( !request.filterBox().isNull() && !tile->boundingVolume()->intersects( request.filterBox() ) )
if ( !request.filterBox().isNull() && !tile->boundingVolume().intersects( request.filterBox() ) )
return;
// TODO -- option to filter out nodes without content
@ -608,10 +641,12 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c
const QgsBox3D rootRegion = QgsCesiumUtils::parseRegion( rootBoundingVolume[ "region" ] );
if ( !rootRegion.isNull() )
{
mBoundingVolume = std::make_unique< QgsTiledSceneBoundingVolumeRegion >( rootRegion );
mBoundingVolume = QgsTiledSceneBoundingVolume( QgsOrientedBox3D::fromBox3D( rootRegion ) );
mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() );
mLayerCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) );
mSceneCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
mLayerMetadata.setCrs( mSceneCrs );
mExtent = rootRegion.toRectangle();
spatialExtent.extentCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4979" ) );
@ -631,16 +666,16 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c
mSceneCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
mLayerMetadata.setCrs( mSceneCrs );
const QgsCoordinateTransform transform( mSceneCrs, mLayerCrs, transformContext );
mBoundingVolume = std::make_unique< QgsTiledSceneBoundingVolumeBox >( bbox );
mBoundingVolume->transform( rootTransform );
mBoundingVolume = QgsTiledSceneBoundingVolume( bbox );
mBoundingVolume.transform( rootTransform );
try
{
const QgsBox3D rootRegion = mBoundingVolume->bounds( transform );
QgsCoordinateTransform ct( mSceneCrs, mLayerCrs, transformContext );
ct.setBallparkTransformsAreAppropriate( true );
const QgsBox3D rootRegion = mBoundingVolume.bounds( ct );
mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() );
std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume->as2DGeometry( transform ) );
std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume.as2DGeometry( ct ) );
mExtent = extent2D->boundingBox();
}
catch ( QgsCsException & )
@ -649,12 +684,12 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c
}
spatialExtent.extentCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
spatialExtent.bounds = mBoundingVolume->bounds();
spatialExtent.bounds = mBoundingVolume.bounds();
}
}
else if ( rootBoundingVolume.contains( "sphere" ) )
{
const QgsSphere sphere = QgsCesiumUtils::parseSphere( rootBoundingVolume["sphere"] );
QgsSphere sphere = QgsCesiumUtils::parseSphere( rootBoundingVolume["sphere"] );
if ( !sphere.isNull() )
{
// layer must advertise as EPSG:4979, as the various QgsMapLayer
@ -665,16 +700,17 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c
mSceneCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
mLayerMetadata.setCrs( mSceneCrs );
const QgsCoordinateTransform transform( mSceneCrs, mLayerCrs, transformContext );
sphere = QgsCesiumUtils::transformSphere( sphere, rootTransform );
mBoundingVolume = std::make_unique< QgsTiledSceneBoundingVolumeSphere >( sphere );
mBoundingVolume->transform( rootTransform );
mBoundingVolume = QgsTiledSceneBoundingVolume( QgsOrientedBox3D::fromBox3D( sphere.boundingBox() ) );
try
{
const QgsBox3D rootRegion = mBoundingVolume->bounds( transform );
QgsCoordinateTransform ct( mSceneCrs, mLayerCrs, transformContext );
ct.setBallparkTransformsAreAppropriate( true );
const QgsBox3D rootRegion = mBoundingVolume.bounds( ct );
mZRange = QgsDoubleRange( rootRegion.zMinimum(), rootRegion.zMaximum() );
std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume->as2DGeometry( transform ) );
std::unique_ptr< QgsAbstractGeometry > extent2D( mBoundingVolume.as2DGeometry( ct ) );
mExtent = extent2D->boundingBox();
}
catch ( QgsCsException & )
@ -683,7 +719,7 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c
}
spatialExtent.extentCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4978" ) );
spatialExtent.bounds = mBoundingVolume->bounds();
spatialExtent.bounds = mBoundingVolume.bounds();
}
}
else
@ -702,7 +738,8 @@ void QgsCesiumTilesDataProviderSharedData::initialize( const QString &tileset, c
mTileset,
rootPath,
authCfg,
headers
headers,
transformContext
)
);
}
@ -935,14 +972,15 @@ const QgsCoordinateReferenceSystem QgsCesiumTilesDataProvider::sceneCrs() const
return mShared->mSceneCrs ;
}
const QgsAbstractTiledSceneBoundingVolume *QgsCesiumTilesDataProvider::boundingVolume() const
const QgsTiledSceneBoundingVolume &QgsCesiumTilesDataProvider::boundingVolume() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
static QgsTiledSceneBoundingVolume nullVolume;
if ( !mShared )
return nullptr;
return nullVolume;
QgsReadWriteLocker locker( mShared->mReadWriteLock, QgsReadWriteLocker::Read );
return mShared ? mShared->mBoundingVolume.get() : nullptr;
return mShared ? mShared->mBoundingVolume : nullVolume;
}
QgsTiledSceneIndex QgsCesiumTilesDataProvider::index() const

View File

@ -56,7 +56,7 @@ class CORE_EXPORT QgsCesiumTilesDataProvider final: public QgsTiledSceneDataProv
QString htmlMetadata() const final;
QgsLayerMetadata layerMetadata() const override;
const QgsCoordinateReferenceSystem sceneCrs() const final;
const QgsAbstractTiledSceneBoundingVolume *boundingVolume() const final;
const QgsTiledSceneBoundingVolume &boundingVolume() const final;
QgsTiledSceneIndex index() const final;
private:

View File

@ -19,6 +19,7 @@
#include "qgscesiumutils.h"
#include "nlohmann/json.hpp"
#include "qgsjsonutils.h"
#include "qgsmatrix4x4.h"
#include "qgssphere.h"
#include "qgsorientedbox3d.h"
@ -114,6 +115,32 @@ QgsSphere QgsCesiumUtils::parseSphere( const QVariantList &sphere )
return parseSphere( QgsJsonUtils::jsonFromVariant( sphere ) );
}
QgsSphere QgsCesiumUtils::transformSphere( const QgsSphere &sphere, const QgsMatrix4x4 &transform )
{
if ( !transform.isIdentity() )
{
// center is transformed, radius is scaled by maximum scalar from transform
// see https://github.com/CesiumGS/cesium-native/blob/fd20f5e272850dde6b58c74059e6de767fe25df6/Cesium3DTilesSelection/src/BoundingVolume.cpp#L33
const QgsVector3D center = transform.map( sphere.centerVector() );
const double uniformScale = std::max(
std::max(
std::sqrt(
transform.constData()[0] * transform.constData()[0] +
transform.constData()[1] * transform.constData()[1] +
transform.constData()[2] * transform.constData()[2] ),
std::sqrt(
transform.constData()[4] * transform.constData()[4] +
transform.constData()[5] * transform.constData()[5] +
transform.constData()[6] * transform.constData()[6] ) ),
std::sqrt(
transform.constData()[8] * transform.constData()[8] +
transform.constData()[9] * transform.constData()[9] +
transform.constData()[10] * transform.constData()[10] ) );
return QgsSphere( center.x(), center.y(), center.z(), sphere.radius() * uniformScale );
}
return sphere;
}
QByteArray QgsCesiumUtils::extractGltfFromB3dm( const QByteArray &tileContent )
{

View File

@ -30,6 +30,7 @@ using namespace nlohmann;
class QgsSphere;
class QgsOrientedBox3D;
class QgsMatrix4x4;
/**
* \brief Contains utilities for working with Cesium data.
@ -87,6 +88,11 @@ class CORE_EXPORT QgsCesiumUtils
*/
static QgsSphere parseSphere( const QVariantList &sphere );
/**
* Applies a \a transform to a sphere.
*/
static QgsSphere transformSphere( const QgsSphere &sphere, const QgsMatrix4x4 &transform );
/**
* Extracts GLTF binary data from the legacy b3dm (Batched 3D Model) tile format.
* Returns empty byte array on error.

View File

@ -16,147 +16,24 @@
***************************************************************************/
#include "qgstiledsceneboundingvolume.h"
#include "qgscircle.h"
#include "qgscoordinatetransform.h"
#include "qgsmatrix4x4.h"
#include "qgsvector3d.h"
#include "qgsmultipoint.h"
#include "qgsgeos.h"
#include "qgspolygon.h"
QgsAbstractTiledSceneBoundingVolume::~QgsAbstractTiledSceneBoundingVolume() = default;
//
// QgsTiledSceneBoundingVolumeRegion
//
QgsTiledSceneBoundingVolumeRegion::QgsTiledSceneBoundingVolumeRegion( const QgsBox3D &region )
: mRegion( region )
{
}
Qgis::TiledSceneBoundingVolumeType QgsTiledSceneBoundingVolumeRegion::type() const
{
return Qgis::TiledSceneBoundingVolumeType::Region;
}
void QgsTiledSceneBoundingVolumeRegion::transform( const QgsMatrix4x4 &transform )
{
// Regions are not transformed. See https://github.com/CesiumGS/cesium-native/blob/fd20f5e272850dde6b58c74059e6de767fe25df6/Cesium3DTilesSelection/src/BoundingVolume.cpp#L28C6-L28C38
( void ) transform;
}
QgsBox3D QgsTiledSceneBoundingVolumeRegion::bounds( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) const
{
if ( transform.isValid() && !transform.isShortCircuited() )
{
// transform each corner of the box, then collect the min/max x/y/z values of the result
QVector<QgsVector3D > corners = mRegion.corners();
QVector< double > x;
x.reserve( 8 );
QVector< double > y;
y.reserve( 8 );
QVector< double > z;
z.reserve( 8 );
for ( int i = 0; i < 8; ++i )
{
const QgsVector3D corner = corners[i];
x.append( corner.x() );
y.append( corner.y() );
z.append( corner.z() );
}
transform.transformInPlace( x, y, z, direction );
const auto minMaxX = std::minmax_element( x.constBegin(), x.constEnd() );
const auto minMaxY = std::minmax_element( y.constBegin(), y.constEnd() );
const auto minMaxZ = std::minmax_element( z.constBegin(), z.constEnd() );
return QgsBox3D( *minMaxX.first, *minMaxY.first, *minMaxZ.first, *minMaxX.second, *minMaxY.second, *minMaxZ.second );
}
else
{
return mRegion;
}
}
QgsTiledSceneBoundingVolumeRegion *QgsTiledSceneBoundingVolumeRegion::clone() const
{
return new QgsTiledSceneBoundingVolumeRegion( *this );
}
QgsAbstractGeometry *QgsTiledSceneBoundingVolumeRegion::as2DGeometry( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) const
{
if ( transform.isValid() && !transform.isShortCircuited() )
{
const QVector< QgsVector3D > corners = mRegion.corners();
QVector< double > x;
x.reserve( 8 );
QVector< double > y;
y.reserve( 8 );
QVector< double > z;
z.reserve( 8 );
for ( int i = 0; i < 8; ++i )
{
const QgsVector3D &corner = corners[i];
x.append( corner.x() );
y.append( corner.y() );
z.append( corner.z() );
}
if ( transform.isValid() && !transform.isShortCircuited() )
{
transform.transformInPlace( x, y, z, direction );
}
std::unique_ptr< QgsMultiPoint > mp = std::make_unique< QgsMultiPoint >( x, y );
QgsGeos geosMp( mp.get() );
return geosMp.convexHull();
}
else
{
std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
std::unique_ptr< QgsLineString > ext = std::make_unique< QgsLineString >(
QVector< double >() << mRegion.xMinimum()
<< mRegion.xMaximum()
<< mRegion.xMaximum()
<< mRegion.xMinimum()
<< mRegion.xMinimum(),
QVector< double >() << mRegion.yMinimum()
<< mRegion.yMinimum()
<< mRegion.yMaximum()
<< mRegion.yMaximum()
<< mRegion.yMinimum() );
polygon->setExteriorRing( ext.release() );
return polygon.release();
}
}
bool QgsTiledSceneBoundingVolumeRegion::intersects( const QgsOrientedBox3D &box ) const
{
return QgsOrientedBox3D::fromBox3D( mRegion ).intersects( box );
}
//
// QgsTiledSceneBoundingVolumeBox
//
QgsTiledSceneBoundingVolumeBox::QgsTiledSceneBoundingVolumeBox( const QgsOrientedBox3D &box )
QgsTiledSceneBoundingVolume::QgsTiledSceneBoundingVolume( const QgsOrientedBox3D &box )
: mBox( box )
{
}
Qgis::TiledSceneBoundingVolumeType QgsTiledSceneBoundingVolumeBox::type() const
{
return Qgis::TiledSceneBoundingVolumeType::OrientedBox;
}
void QgsTiledSceneBoundingVolumeBox::transform( const QgsMatrix4x4 &transform )
void QgsTiledSceneBoundingVolume::transform( const QgsMatrix4x4 &transform )
{
mBox = mBox.transformed( transform );
}
QgsBox3D QgsTiledSceneBoundingVolumeBox::bounds( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) const
QgsBox3D QgsTiledSceneBoundingVolume::bounds( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) const
{
if ( transform.isValid() && !transform.isShortCircuited() )
{
@ -187,12 +64,7 @@ QgsBox3D QgsTiledSceneBoundingVolumeBox::bounds( const QgsCoordinateTransform &t
}
}
QgsTiledSceneBoundingVolumeBox *QgsTiledSceneBoundingVolumeBox::clone() const
{
return new QgsTiledSceneBoundingVolumeBox( *this );
}
QgsAbstractGeometry *QgsTiledSceneBoundingVolumeBox::as2DGeometry( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) const
QgsAbstractGeometry *QgsTiledSceneBoundingVolume::as2DGeometry( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) const
{
std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
@ -221,135 +93,8 @@ QgsAbstractGeometry *QgsTiledSceneBoundingVolumeBox::as2DGeometry( const QgsCoor
return geosMp.convexHull();
}
bool QgsTiledSceneBoundingVolumeBox::intersects( const QgsOrientedBox3D &box ) const
bool QgsTiledSceneBoundingVolume::intersects( const QgsOrientedBox3D &box ) const
{
return mBox.intersects( box );
}
//
// QgsTiledSceneBoundingVolumeSphere
//
QgsTiledSceneBoundingVolumeSphere::QgsTiledSceneBoundingVolumeSphere( const QgsSphere &sphere )
: mSphere( sphere )
{
}
Qgis::TiledSceneBoundingVolumeType QgsTiledSceneBoundingVolumeSphere::type() const
{
return Qgis::TiledSceneBoundingVolumeType::Sphere;
}
void QgsTiledSceneBoundingVolumeSphere::transform( const QgsMatrix4x4 &transform )
{
// center is transformed, radius is scaled by maximum scalar from transform
// see https://github.com/CesiumGS/cesium-native/blob/fd20f5e272850dde6b58c74059e6de767fe25df6/Cesium3DTilesSelection/src/BoundingVolume.cpp#L33
const QgsVector3D center = transform.map( mSphere.centerVector() );
const double uniformScale = std::max(
std::max(
std::sqrt(
transform.constData()[0] * transform.constData()[0] +
transform.constData()[1] * transform.constData()[1] +
transform.constData()[2] * transform.constData()[2] +
transform.constData()[3] * transform.constData()[3] ),
std::sqrt(
transform.constData()[4] * transform.constData()[4] +
transform.constData()[5] * transform.constData()[5] +
transform.constData()[6] * transform.constData()[6] +
transform.constData()[7] * transform.constData()[7] ) ),
std::sqrt(
transform.constData()[8] * transform.constData()[8] +
transform.constData()[9] * transform.constData()[9] +
transform.constData()[10] * transform.constData()[10] +
transform.constData()[11] * transform.constData()[11] ) );
mSphere = QgsSphere( center.x(), center.y(), center.z(), mSphere.radius() * uniformScale );
}
QgsBox3D QgsTiledSceneBoundingVolumeSphere::bounds( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) const
{
if ( transform.isValid() && !transform.isShortCircuited() )
{
const QVector< QgsVector3D > corners = mSphere.boundingBox().corners();
QVector< double > x;
x.reserve( 8 );
QVector< double > y;
y.reserve( 8 );
QVector< double > z;
z.reserve( 8 );
for ( int i = 0; i < 8; ++i )
{
const QgsVector3D &corner = corners[i];
x.append( corner.x() );
y.append( corner.y() );
z.append( corner.z() );
}
transform.transformInPlace( x, y, z, direction );
const auto minMaxX = std::minmax_element( x.constBegin(), x.constEnd() );
const auto minMaxY = std::minmax_element( y.constBegin(), y.constEnd() );
const auto minMaxZ = std::minmax_element( z.constBegin(), z.constEnd() );
return QgsBox3D( *minMaxX.first, *minMaxY.first, *minMaxZ.first, *minMaxX.second, *minMaxY.second, *minMaxZ.second );
}
else
{
return mSphere.boundingBox();
}
}
QgsTiledSceneBoundingVolumeSphere *QgsTiledSceneBoundingVolumeSphere::clone() const
{
return new QgsTiledSceneBoundingVolumeSphere( *this );
}
QgsAbstractGeometry *QgsTiledSceneBoundingVolumeSphere::as2DGeometry( const QgsCoordinateTransform &transform, Qgis::TransformDirection direction ) const
{
if ( transform.isValid() && !transform.isShortCircuited() )
{
const QgsVector3D sphereCenter = mSphere.centerVector();
QgsVector3D normal = sphereCenter;
normal.normalize();
QgsVector3D axis1 = QgsVector3D::crossProduct( normal, QgsVector3D( 1, 0, 0 ) );
if ( axis1.length() == 0 )
{
axis1 = QgsVector3D::crossProduct( normal, QgsVector3D( 0, 1, 0 ) );
}
axis1.normalize();
const QgsVector3D axis2 = QgsVector3D::crossProduct( normal, axis1 );
QVector< double > circleXInPlane;
QVector< double > circleYInPlane;
QVector< double > circleZInPlane;
for ( int i = 0; i < 48; ++i )
{
const double alpha = 2 * i / 48.0 * M_PI;
circleXInPlane.append( mSphere.centerX() + mSphere.radius() * ( axis1.x() * std::cos( alpha ) + axis2.x()* std::sin( alpha ) ) );
circleYInPlane.append( mSphere.centerY() + mSphere.radius() * ( axis1.y() * std::cos( alpha ) + axis2.y()* std::sin( alpha ) ) );
circleZInPlane.append( mSphere.centerZ() + mSphere.radius() * ( axis1.z() * std::cos( alpha ) + axis2.z()* std::sin( alpha ) ) );
}
transform.transformInPlace( circleXInPlane, circleYInPlane, circleZInPlane, direction );
std::unique_ptr< QgsLineString > exterior = std::make_unique< QgsLineString>( circleXInPlane, circleYInPlane );
exterior->close();
std::unique_ptr< QgsPolygon > polygon = std::make_unique< QgsPolygon >();
polygon->setExteriorRing( exterior.release() );
return polygon.release();
}
else
{
std::unique_ptr< QgsCurvePolygon > polygon = std::make_unique< QgsCurvePolygon >();
std::unique_ptr< QgsCircularString > exterior( mSphere.toCircle().toCircularString() );
polygon->setExteriorRing( exterior.release() );
return polygon.release();
}
}
bool QgsTiledSceneBoundingVolumeSphere::intersects( const QgsOrientedBox3D &box ) const
{
// just a simple "bounding box of sphere" intersects test for now -- this could obviously be refined, but it's likely not necessary...
const QgsBox3D boundingBox = mSphere.boundingBox();
return QgsOrientedBox3D::fromBox3D( boundingBox ).intersects( box );
}

View File

@ -24,7 +24,6 @@
#include "qgis.h"
#include "qgsbox3d.h"
#include "qgsmatrix4x4.h"
#include "qgssphere.h"
#include "qgsorientedbox3d.h"
#include "qgscoordinatetransform.h"
@ -32,40 +31,18 @@ class QgsMatrix4x4;
/**
* \ingroup core
* \brief Abstract base class for bounding volumes for tiled scene nodes.
* \brief Represents a bounding volume for a tiled scene.
*
* \since QGIS 3.34
*/
class CORE_EXPORT QgsAbstractTiledSceneBoundingVolume
class CORE_EXPORT QgsTiledSceneBoundingVolume
{
public:
#ifdef SIP_RUN
SIP_CONVERT_TO_SUBCLASS_CODE
switch ( sipCpp->type() )
{
case Qgis::TiledSceneBoundingVolumeType::Region:
sipType = sipType_QgsTiledSceneBoundingVolumeRegion;
break;
case Qgis::TiledSceneBoundingVolumeType::OrientedBox:
sipType = sipType_QgsTiledSceneBoundingVolumeBox;
break;
case Qgis::TiledSceneBoundingVolumeType::Sphere:
sipType = sipType_QgsTiledSceneBoundingVolumeSphere;
break;
default:
sipType = 0;
break;
};
SIP_END
#endif
virtual ~QgsAbstractTiledSceneBoundingVolume();
/**
* Returns the type of the volume;
* Constructor for QgsTiledSceneBoundingVolume, with the specified oriented \a box.
*/
virtual Qgis::TiledSceneBoundingVolumeType type() const = 0;
QgsTiledSceneBoundingVolume( const QgsOrientedBox3D &box = QgsOrientedBox3D() );
/**
* Returns the axis aligned bounding box of the volume.
@ -73,12 +50,7 @@ class CORE_EXPORT QgsAbstractTiledSceneBoundingVolume
* The optional \a transform and \a direction arguments should be used whenever the volume needs
* to be transformed into a specific destination CRS, in order to correctly handle 3D coordinate transforms.
*/
virtual QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const = 0;
/**
* Returns a clone of the volume.
*/
virtual QgsAbstractTiledSceneBoundingVolume *clone() const = 0 SIP_FACTORY;
QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const;
/**
* Returns a new geometry representing the 2-dimensional X/Y center slice of the volume.
@ -88,77 +60,17 @@ class CORE_EXPORT QgsAbstractTiledSceneBoundingVolume
* The optional \a transform and \a direction arguments should be used whenever the volume needs
* to be transformed into a specific destination CRS, in order to correctly handle 3D coordinate transforms.
*/
virtual QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const = 0 SIP_FACTORY;
QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const SIP_FACTORY;
/**
* Applies a \a transform to the bounding volume.
*
* The actual result of transforming a bounding volume depends on subclass specific logic. For instance:
*
* - transforming a QgsTiledSceneBoundingVolumeRegion results in no change to the region
* - transforming a QgsTiledSceneBoundingVolumeSphere causes the radius to be multiplied by the maximum length of the transform scales
*/
virtual void transform( const QgsMatrix4x4 &transform ) = 0;
void transform( const QgsMatrix4x4 &transform );
/**
* Returns TRUE if this bounds intersects the specified \a box.
*/
virtual bool intersects( const QgsOrientedBox3D &box ) const = 0;
};
/**
* \ingroup core
* \brief A region bounding volume for tiled scene nodes.
*
* \since QGIS 3.34
*/
class CORE_EXPORT QgsTiledSceneBoundingVolumeRegion : public QgsAbstractTiledSceneBoundingVolume
{
public:
/**
* Constructor for QgsTiledSceneBoundingVolumeRegion, with the specified \a region.
*/
QgsTiledSceneBoundingVolumeRegion( const QgsBox3D &region );
Qgis::TiledSceneBoundingVolumeType type() const FINAL;
void transform( const QgsMatrix4x4 &transform ) FINAL;
QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const FINAL SIP_THROW( QgsCsException );
QgsTiledSceneBoundingVolumeRegion *clone() const FINAL SIP_FACTORY;
QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const FINAL SIP_THROW( QgsCsException ) SIP_FACTORY;
bool intersects( const QgsOrientedBox3D &box ) const FINAL;
/**
* Returns the volume's region.
*/
QgsBox3D region() const { return mRegion; }
private:
QgsBox3D mRegion;
};
/**
* \ingroup core
* \brief A oriented box bounding volume for tiled scene nodes.
*
* \since QGIS 3.34
*/
class CORE_EXPORT QgsTiledSceneBoundingVolumeBox : public QgsAbstractTiledSceneBoundingVolume
{
public:
/**
* Constructor for QgsTiledSceneBoundingVolumeBox, with the specified oriented \a box.
*/
QgsTiledSceneBoundingVolumeBox( const QgsOrientedBox3D &box );
Qgis::TiledSceneBoundingVolumeType type() const FINAL;
void transform( const QgsMatrix4x4 &transform ) FINAL;
QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const FINAL SIP_THROW( QgsCsException );
QgsTiledSceneBoundingVolumeBox *clone() const FINAL SIP_FACTORY;
QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const FINAL SIP_THROW( QgsCsException ) SIP_FACTORY;
bool intersects( const QgsOrientedBox3D &box ) const FINAL;
bool intersects( const QgsOrientedBox3D &box ) const;
/**
* Returns the volume's oriented box.
@ -171,38 +83,4 @@ class CORE_EXPORT QgsTiledSceneBoundingVolumeBox : public QgsAbstractTiledSceneB
};
/**
* \ingroup core
* \brief A spherical bounding volume for tiled scene nodes.
*
* \since QGIS 3.34
*/
class CORE_EXPORT QgsTiledSceneBoundingVolumeSphere: public QgsAbstractTiledSceneBoundingVolume
{
public:
/**
* Constructor for QgsTiledSceneBoundingVolumeSphere, with the specified \a sphere.
*/
QgsTiledSceneBoundingVolumeSphere( const QgsSphere &sphere );
Qgis::TiledSceneBoundingVolumeType type() const FINAL;
void transform( const QgsMatrix4x4 &transform ) FINAL;
QgsBox3D bounds( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const FINAL SIP_THROW( QgsCsException );
QgsTiledSceneBoundingVolumeSphere *clone() const FINAL SIP_FACTORY;
QgsAbstractGeometry *as2DGeometry( const QgsCoordinateTransform &transform = QgsCoordinateTransform(), Qgis::TransformDirection direction = Qgis::TransformDirection::Forward ) const FINAL SIP_THROW( QgsCsException ) SIP_FACTORY;
bool intersects( const QgsOrientedBox3D &box ) const FINAL;
/**
* Returns the volume's sphere.
*/
QgsSphere sphere() const { return mSphere; }
private:
QgsSphere mSphere;
};
#endif // QGSTILEDSCENEBOUNDINGVOLUME_H

View File

@ -23,7 +23,7 @@
#include "qgsdataprovider.h"
#include "qgis.h"
class QgsAbstractTiledSceneBoundingVolume;
class QgsTiledSceneBoundingVolume;
class QgsTiledSceneIndex;
/**
@ -89,7 +89,7 @@ class CORE_EXPORT QgsTiledSceneDataProvider: public QgsDataProvider
*
* \warning Coordinates in the returned volume are in the sceneCrs() reference system, not the QgsDataProvider::crs() system.
*/
virtual const QgsAbstractTiledSceneBoundingVolume *boundingVolume() const = 0;
virtual const QgsTiledSceneBoundingVolume &boundingVolume() const = 0;
/**
* Returns the provider's tile index.

View File

@ -41,8 +41,7 @@ QgsTiledSceneLayerRenderer::QgsTiledSceneLayerRenderer( QgsTiledSceneLayer *laye
mSceneCrs = layer->dataProvider()->sceneCrs();
mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer );
if ( const QgsAbstractTiledSceneBoundingVolume *layerBoundingVolume = layer->dataProvider()->boundingVolume() )
mLayerBoundingVolume.reset( layerBoundingVolume->clone() );
mLayerBoundingVolume = layer->dataProvider()->boundingVolume();
mReadyToCompose = false;
}
@ -79,39 +78,36 @@ bool QgsTiledSceneLayerRenderer::render()
bool canceled = false;
if ( mLayerBoundingVolume )
const QgsCoordinateTransform transform = QgsCoordinateTransform( mSceneCrs, renderContext()->coordinateTransform().destinationCrs(), renderContext()->transformContext() );
try
{
const QgsCoordinateTransform transform = QgsCoordinateTransform( mSceneCrs, renderContext()->coordinateTransform().destinationCrs(), renderContext()->transformContext() );
try
std::unique_ptr< QgsAbstractGeometry > rootBoundsGeometry( mLayerBoundingVolume.as2DGeometry( transform ) );
if ( QgsCurvePolygon *polygon = qgsgeometry_cast< QgsCurvePolygon * >( rootBoundsGeometry.get() ) )
{
std::unique_ptr< QgsAbstractGeometry > rootBoundsGeometry( mLayerBoundingVolume->as2DGeometry( transform ) );
if ( QgsCurvePolygon *polygon = qgsgeometry_cast< QgsCurvePolygon * >( rootBoundsGeometry.get() ) )
QPolygonF rootBoundsPoly = polygon->exteriorRing()->asQPolygonF();
// remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
rootBoundsPoly.erase( std::remove_if( rootBoundsPoly.begin(), rootBoundsPoly.end(),
[]( const QPointF point )
{
QPolygonF rootBoundsPoly = polygon->exteriorRing()->asQPolygonF();
return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
} ), rootBoundsPoly.end() );
// remove non-finite points, e.g. infinite or NaN points caused by reprojecting errors
rootBoundsPoly.erase( std::remove_if( rootBoundsPoly.begin(), rootBoundsPoly.end(),
[]( const QPointF point )
{
return !std::isfinite( point.x() ) || !std::isfinite( point.y() );
} ), rootBoundsPoly.end() );
QPointF *ptr = rootBoundsPoly.data();
for ( int i = 0; i < rootBoundsPoly.size(); ++i, ++ptr )
{
renderContext()->mapToPixel().transformInPlace( ptr->rx(), ptr->ry() );
}
QgsLineSymbol symbol;
symbol.startRender( *renderContext() );
symbol.renderPolyline( rootBoundsPoly, nullptr, *renderContext() );
symbol.stopRender( *renderContext() );
QPointF *ptr = rootBoundsPoly.data();
for ( int i = 0; i < rootBoundsPoly.size(); ++i, ++ptr )
{
renderContext()->mapToPixel().transformInPlace( ptr->rx(), ptr->ry() );
}
QgsLineSymbol symbol;
symbol.startRender( *renderContext() );
symbol.renderPolyline( rootBoundsPoly, nullptr, *renderContext() );
symbol.stopRender( *renderContext() );
}
catch ( QgsCsException & )
{
QgsDebugError( QStringLiteral( "Error transforming root bounding volume" ) );
}
}
catch ( QgsCsException & )
{
QgsDebugError( QStringLiteral( "Error transforming root bounding volume" ) );
}
mRenderer->stopRender( context );

View File

@ -21,6 +21,7 @@
#include "qgis_core.h"
#include "qgsmaplayerrenderer.h"
#include "qgscoordinatereferencesystem.h"
#include "qgstiledsceneboundingvolume.h"
#include <memory>
#include <QElapsedTimer>
@ -30,7 +31,6 @@
class QgsTiledSceneLayer;
class QgsFeedback;
class QgsMapClippingRegion;
class QgsAbstractTiledSceneBoundingVolume;
class QgsTiledSceneRenderer;
@ -68,7 +68,7 @@ class CORE_EXPORT QgsTiledSceneLayerRenderer: public QgsMapLayerRenderer
QElapsedTimer mElapsedTimer;
QgsCoordinateReferenceSystem mSceneCrs;
std::unique_ptr< QgsAbstractTiledSceneBoundingVolume > mLayerBoundingVolume;
QgsTiledSceneBoundingVolume mLayerBoundingVolume;
std::unique_ptr<QgsFeedback> mFeedback = nullptr;
};

View File

@ -19,6 +19,8 @@
QgsTiledSceneRequest::QgsTiledSceneRequest() = default;
void QgsTiledSceneRequest::setFeedback( QgsFeedback *feedback )
{
mFeedback = feedback;

View File

@ -21,6 +21,8 @@
#include "qgis_core.h"
#include "qgis.h"
#include "qgscoordinatereferencesystem.h"
#include "qgscoordinatetransformcontext.h"
#include "qgsorientedbox3d.h"
class QgsFeedback;
@ -53,16 +55,19 @@ class CORE_EXPORT QgsTiledSceneRequest
Qgis::TiledSceneRequestFlags flags() const { return mFlags; }
/**
* Returns the box from which data will be taken, in the layer's CRS.
* Returns the box from which data will be taken.
*
* The CRS for the box can be retrieved by filterBoxCrs().
*
* If the returned box is null, then no filter box is set.
*
* \see filterBoxCrs()
* \see setFilterBox()
*/
QgsOrientedBox3D filterBox() const { return mFilterBox; }
/**
* Sets the \a box from which data will be taken, in the layer's CRS.
* Sets the \a box from which data will be taken.
*
* An null \a box removes the filter.
*
@ -131,6 +136,7 @@ class CORE_EXPORT QgsTiledSceneRequest
Qgis::TiledSceneRequestFlags mFlags;
QgsOrientedBox3D mFilterBox;
QgsFeedback *mFeedback = nullptr;
double mRequiredGeometricError = 0;
long long mParentTileId = -1;

View File

@ -19,12 +19,14 @@
#include "qgstiledsceneboundingvolume.h"
QgsTiledSceneTile::QgsTiledSceneTile()
: mBoundingVolume( QgsTiledSceneBoundingVolume( QgsOrientedBox3D() ) )
{
}
QgsTiledSceneTile::QgsTiledSceneTile( long long id )
: mId( id )
, mBoundingVolume( QgsTiledSceneBoundingVolume( QgsOrientedBox3D() ) )
{
}
@ -34,10 +36,10 @@ QgsTiledSceneTile::~QgsTiledSceneTile() = default;
QgsTiledSceneTile::QgsTiledSceneTile( const QgsTiledSceneTile &other )
: mId( other.mId )
, mRefinementProcess( other.mRefinementProcess )
, mBoundingVolume( other.mBoundingVolume )
, mResources( other.mResources )
, mGeometricError( other.mGeometricError )
{
mBoundingVolume.reset( other.mBoundingVolume ? other.mBoundingVolume->clone() : nullptr );
mTransform.reset( other.mTransform ? new QgsMatrix4x4( *other.mTransform.get() ) : nullptr );
}
@ -48,7 +50,7 @@ QgsTiledSceneTile &QgsTiledSceneTile::operator=( const QgsTiledSceneTile &other
mTransform.reset( other.mTransform ? new QgsMatrix4x4( *other.mTransform.get() ) : nullptr );
mResources = other.mResources;
mGeometricError = other.mGeometricError;
mBoundingVolume.reset( other.mBoundingVolume ? other.mBoundingVolume->clone() : nullptr );
mBoundingVolume = other.mBoundingVolume;
return *this;
}
@ -57,14 +59,14 @@ void QgsTiledSceneTile::setRefinementProcess( Qgis::TileRefinementProcess proces
mRefinementProcess = process;
}
void QgsTiledSceneTile::setBoundingVolume( QgsAbstractTiledSceneBoundingVolume *volume )
void QgsTiledSceneTile::setBoundingVolume( const QgsTiledSceneBoundingVolume &volume )
{
mBoundingVolume.reset( volume );
mBoundingVolume = volume;
}
const QgsAbstractTiledSceneBoundingVolume *QgsTiledSceneTile::boundingVolume() const
const QgsTiledSceneBoundingVolume &QgsTiledSceneTile::boundingVolume() const
{
return mBoundingVolume.get();
return mBoundingVolume;
}
void QgsTiledSceneTile::setTransform( const QgsMatrix4x4 &transform )

View File

@ -22,8 +22,7 @@
#include "qgis_core.h"
#include "qgis.h"
#include "qgsmatrix4x4.h"
class QgsAbstractTiledSceneBoundingVolume;
#include "qgstiledsceneboundingvolume.h"
/**
* \ingroup core
@ -89,18 +88,16 @@ class CORE_EXPORT QgsTiledSceneTile
/**
* Sets the bounding \a volume for the tile.
*
* Ownership of \a volume is transferred to the tile.
*
* \see boundingVolume()
*/
void setBoundingVolume( QgsAbstractTiledSceneBoundingVolume *volume SIP_TRANSFER );
void setBoundingVolume( const QgsTiledSceneBoundingVolume &volume );
/**
* Returns the bounding volume for the tile.
*
* \see setBoundingVolume()
*/
const QgsAbstractTiledSceneBoundingVolume *boundingVolume() const;
const QgsTiledSceneBoundingVolume &boundingVolume() const;
/**
* Sets the tile's \a transform.
@ -152,7 +149,7 @@ class CORE_EXPORT QgsTiledSceneTile
private:
long long mId = -1;
Qgis::TileRefinementProcess mRefinementProcess = Qgis::TileRefinementProcess::Replacement;
std::unique_ptr< QgsAbstractTiledSceneBoundingVolume > mBoundingVolume;
QgsTiledSceneBoundingVolume mBoundingVolume;
std::unique_ptr< QgsMatrix4x4 > mTransform;
QVariantMap mResources;
double mGeometricError = 0;

View File

@ -86,28 +86,17 @@ class TestQgsCesium3dTilesLayer(unittest.TestCase):
self.assertAlmostEqual(layer.extent().yMaximum(), 40.044339909, 3)
self.assertAlmostEqual(
layer.dataProvider().boundingVolume().region().xMinimum(),
-75.6144410,
layer.dataProvider().boundingVolume().box().centerX(),
-75.612094,
3,
)
self.assertAlmostEqual(
layer.dataProvider().boundingVolume().region().xMaximum(),
-75.6097475,
layer.dataProvider().boundingVolume().box().centerY(),
40.0425306,
3,
)
self.assertAlmostEqual(
layer.dataProvider().boundingVolume().region().yMinimum(), 40.0407213, 3
)
self.assertAlmostEqual(
layer.dataProvider().boundingVolume().region().yMaximum(),
40.044339909,
3,
)
self.assertAlmostEqual(
layer.dataProvider().boundingVolume().region().zMinimum(), 1.2, 3
)
self.assertAlmostEqual(
layer.dataProvider().boundingVolume().region().zMaximum(), 67.00999, 3
layer.dataProvider().boundingVolume().box().centerZ(), 34.105, 3
)
# check that version, tileset version, and z range are in html metadata
@ -174,13 +163,10 @@ class TestQgsCesium3dTilesLayer(unittest.TestCase):
layer = QgsTiledSceneLayer(tmp_file, "my layer", "cesiumtiles")
self.assertTrue(layer.dataProvider().isValid())
layer_bounds = layer.dataProvider().boundingVolume().region()
self.assertAlmostEqual(layer_bounds.xMinimum(), -75.6132, 4)
self.assertAlmostEqual(layer_bounds.xMaximum(), -75.6075, 4)
self.assertAlmostEqual(layer_bounds.yMinimum(), 40.0383, 4)
self.assertAlmostEqual(layer_bounds.yMaximum(), 40.044, 4)
self.assertAlmostEqual(layer_bounds.zMinimum(), 1.2, 4)
self.assertAlmostEqual(layer_bounds.zMaximum(), 67.01, 4)
layer_bounds = layer.dataProvider().boundingVolume().box()
self.assertAlmostEqual(layer_bounds.centerX(), -75.61037543, 4)
self.assertAlmostEqual(layer_bounds.centerY(), 40.0411555, 4)
self.assertAlmostEqual(layer_bounds.centerZ(), 34.1050000, 4)
def test_source_bounding_volume_box(self):
with tempfile.TemporaryDirectory() as temp_dir:
@ -277,26 +263,23 @@ class TestQgsCesium3dTilesLayer(unittest.TestCase):
self.assertEqual(layer.dataProvider().crs().authid(), "EPSG:4979")
# extent must be in EPSG:4979 to match the layer crs()
self.assertAlmostEqual(layer.extent().xMinimum(), 149.5562895, 3)
self.assertAlmostEqual(layer.extent().xMaximum(), 149.5989376, 3)
self.assertAlmostEqual(layer.extent().yMinimum(), -33.4378807, 3)
self.assertAlmostEqual(layer.extent().yMaximum(), -33.402147, 3)
self.assertAlmostEqual(layer.extent().xMinimum(), 149.5484313, 3)
self.assertAlmostEqual(layer.extent().xMaximum(), 149.60678790, 3)
self.assertAlmostEqual(layer.extent().yMinimum(), -33.4484168, 3)
self.assertAlmostEqual(layer.extent().yMaximum(), -33.391621, 3)
self.assertAlmostEqual(
layer.dataProvider().boundingVolume().sphere().centerX(),
layer.dataProvider().boundingVolume().box().centerX(),
-4595750.5786,
1,
)
self.assertAlmostEqual(
layer.dataProvider().boundingVolume().sphere().centerY(),
layer.dataProvider().boundingVolume().box().centerY(),
2698725.128252,
1,
)
self.assertEqual(
layer.dataProvider().boundingVolume().sphere().centerZ(), -3493318.0
)
self.assertEqual(
layer.dataProvider().boundingVolume().sphere().radius(), 1983.0
layer.dataProvider().boundingVolume().box().centerZ(), -3493318.0
)
# check that version, tileset version, and z range are in html metadata

View File

@ -18,9 +18,7 @@ from qgis.core import (
Qgis,
QgsSphere,
QgsOrientedBox3D,
QgsTiledSceneBoundingVolumeSphere,
QgsTiledSceneBoundingVolumeRegion,
QgsTiledSceneBoundingVolumeBox,
QgsTiledSceneBoundingVolume,
QgsBox3d,
QgsCoordinateReferenceSystem,
QgsCoordinateTransform,
@ -36,82 +34,18 @@ TEST_DATA_DIR = unitTestDataPath()
class TestQgsTiledSceneBoundingVolume(QgisTestCase):
def test_region(self):
volume = QgsTiledSceneBoundingVolumeRegion(QgsBox3d(1, 2, 3, 10, 11, 12))
self.assertEqual(volume.type(), Qgis.TiledSceneBoundingVolumeType.Region)
volume.transform(QgsMatrix4x4(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0))
# should be no change when transforming regions!
self.assertEqual(volume.region(), QgsBox3d(1, 2, 3, 10, 11, 12))
cloned = volume.clone()
self.assertIsInstance(cloned, QgsTiledSceneBoundingVolumeRegion)
self.assertEqual(cloned.region(), QgsBox3d(1, 2, 3, 10, 11, 12))
# bounds
bounds = volume.bounds()
self.assertEqual(bounds.xMinimum(), 1)
self.assertEqual(bounds.xMaximum(), 10)
self.assertEqual(bounds.yMinimum(), 2)
self.assertEqual(bounds.yMaximum(), 11)
self.assertEqual(bounds.zMinimum(), 3)
self.assertEqual(bounds.zMaximum(), 12)
geometry_2d = volume.as2DGeometry()
self.assertEqual(geometry_2d.asWkt(), "Polygon ((1 2, 10 2, 10 11, 1 11, 1 2))")
# with coordinate transform
volume = QgsTiledSceneBoundingVolumeRegion(
QgsBox3d(
-4595750,
2698725,
-3493318,
-4595750 + 1000,
2698725 + 1500,
-3493318 + 2000,
)
)
transform = QgsCoordinateTransform(
QgsCoordinateReferenceSystem("EPSG:4978"),
QgsCoordinateReferenceSystem("EPSG:4979"),
QgsCoordinateTransformContext(),
)
bounds = volume.bounds(transform)
self.assertAlmostEqual(bounds.xMinimum(), 149.5582617, 3)
self.assertAlmostEqual(bounds.xMaximum(), 149.577611, 3)
self.assertAlmostEqual(bounds.yMinimum(), -33.424296, 3)
self.assertAlmostEqual(bounds.yMaximum(), -33.4011944, 3)
self.assertAlmostEqual(bounds.zMinimum(), -1122.81806, 3)
self.assertAlmostEqual(bounds.zMaximum(), 1332.44347, 3)
geometry_2d = volume.as2DGeometry(transform)
self.assertEqual(
geometry_2d.asWkt(3),
"Polygon ((149.572 -33.424, 149.558 -33.421, 149.558 -33.405, 149.564 -33.401, 149.578 -33.405, 149.578 -33.42, 149.572 -33.424))",
)
def test_region_intersects(self):
volume = QgsTiledSceneBoundingVolumeRegion(QgsBox3d(1, 2, 3, 10, 11, 12))
self.assertFalse(
volume.intersects(
QgsOrientedBox3D.fromBox3D(QgsBox3d(11, 2, 3, 10, 11, 12))
)
)
self.assertTrue(
volume.intersects(QgsOrientedBox3D.fromBox3D(QgsBox3d(9, 2, 3, 10, 11, 12)))
)
def test_box(self):
volume = QgsTiledSceneBoundingVolumeBox(
volume = QgsTiledSceneBoundingVolume(
QgsOrientedBox3D([1, 2, 3], [10, 0, 0, 0, 20, 0, 0, 0, 30])
)
self.assertEqual(volume.type(), Qgis.TiledSceneBoundingVolumeType.OrientedBox)
volume.transform(QgsMatrix4x4(1.5, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1))
self.assertEqual(
volume.box(), QgsOrientedBox3D([1.5, 4, 9], [15, 0, 0, 0, 40, 0, 0, 0, 90])
)
cloned = volume.clone()
self.assertIsInstance(cloned, QgsTiledSceneBoundingVolumeBox)
cloned = QgsTiledSceneBoundingVolume(volume)
self.assertIsInstance(cloned, QgsTiledSceneBoundingVolume)
self.assertEqual(
cloned.box(), QgsOrientedBox3D([1.5, 4, 9], [15, 0, 0, 0, 40, 0, 0, 0, 90])
)
@ -132,7 +66,7 @@ class TestQgsTiledSceneBoundingVolume(QgisTestCase):
)
# with coordinate transform
volume = QgsTiledSceneBoundingVolumeBox(
volume = QgsTiledSceneBoundingVolume(
QgsOrientedBox3D(
[-4595750, 2698725, -3493318], [1000, 0, 0, 0, 1500, 0, 0, 0, 2000]
)
@ -157,7 +91,7 @@ class TestQgsTiledSceneBoundingVolume(QgisTestCase):
)
def test_box_intersects(self):
volume = QgsTiledSceneBoundingVolumeBox(
volume = QgsTiledSceneBoundingVolume(
QgsOrientedBox3D(
[1, 1, 1],
[
@ -182,7 +116,7 @@ class TestQgsTiledSceneBoundingVolume(QgisTestCase):
)
)
volume = QgsTiledSceneBoundingVolumeBox(
volume = QgsTiledSceneBoundingVolume(
QgsOrientedBox3D(
[1, 1, 1], [0.7071, 0, 0.7071, 0, 3, 0, -0.7071 * 2, 0, 0.7071 * 2]
)
@ -195,67 +129,6 @@ class TestQgsTiledSceneBoundingVolume(QgisTestCase):
)
)
def test_sphere(self):
volume = QgsTiledSceneBoundingVolumeSphere(QgsSphere(1, 2, 3, 10))
self.assertEqual(volume.type(), Qgis.TiledSceneBoundingVolumeType.Sphere)
volume.transform(QgsMatrix4x4(1.5, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 1))
self.assertEqual(volume.sphere(), QgsSphere(1.5, 4, 9, 30))
cloned = volume.clone()
self.assertIsInstance(cloned, QgsTiledSceneBoundingVolumeSphere)
self.assertEqual(cloned.sphere(), QgsSphere(1.5, 4, 9, 30))
# bounds
bounds = volume.bounds()
self.assertEqual(bounds.xMinimum(), -28.5)
self.assertEqual(bounds.xMaximum(), 31.5)
self.assertEqual(bounds.yMinimum(), -26.0)
self.assertEqual(bounds.yMaximum(), 34.0)
self.assertEqual(bounds.zMinimum(), -21.0)
self.assertEqual(bounds.zMaximum(), 39)
geometry_2d = volume.as2DGeometry()
self.assertEqual(
geometry_2d.asWkt(),
"CurvePolygon (CircularString (1.5 34, 31.5 4, 1.5 -26, -28.5 4, 1.5 34))",
)
# with coordinate transform
volume = QgsTiledSceneBoundingVolumeSphere(
QgsSphere(-4595750, 2698725, -3493318, 1983)
)
transform = QgsCoordinateTransform(
QgsCoordinateReferenceSystem("EPSG:4978"),
QgsCoordinateReferenceSystem("EPSG:4979"),
QgsCoordinateTransformContext(),
)
bounds = volume.bounds(transform)
self.assertAlmostEqual(bounds.xMinimum(), 149.5484294320, 3)
self.assertAlmostEqual(bounds.xMaximum(), 149.606785943, 3)
self.assertAlmostEqual(bounds.yMinimum(), -33.448419, 3)
self.assertAlmostEqual(bounds.yMaximum(), -33.39162, 3)
self.assertAlmostEqual(bounds.zMinimum(), -2659.1526749, 3)
self.assertAlmostEqual(bounds.zMaximum(), 4055.8967716, 3)
geometry_2d = volume.as2DGeometry(transform)
self.assertEqual(
geometry_2d.asWkt(3),
"Polygon ((149.592 -33.433, 149.594 -33.431, 149.596 -33.429, 149.597 -33.427, 149.598 -33.425, 149.599 -33.423, 149.599 -33.421, 149.599 -33.418, 149.598 -33.416, 149.598 -33.414, 149.596 -33.412, 149.595 -33.41, 149.593 -33.408, 149.591 -33.406, 149.589 -33.405, 149.586 -33.404, 149.584 -33.403, 149.581 -33.402, 149.578 -33.402, 149.576 -33.402, 149.573 -33.403, 149.57 -33.403, 149.568 -33.404, 149.565 -33.405, 149.563 -33.407, 149.561 -33.409, 149.56 -33.411, 149.558 -33.413, 149.557 -33.415, 149.557 -33.417, 149.556 -33.419, 149.556 -33.422, 149.557 -33.424, 149.558 -33.426, 149.559 -33.428, 149.56 -33.43, 149.562 -33.432, 149.564 -33.434, 149.566 -33.435, 149.569 -33.436, 149.571 -33.437, 149.574 -33.438, 149.577 -33.438, 149.58 -33.438, 149.582 -33.437, 149.585 -33.437, 149.588 -33.436, 149.59 -33.435, 149.592 -33.433))",
)
def test_sphere_intersects(self):
volume = QgsTiledSceneBoundingVolumeSphere(QgsSphere(1, 2, 3, 6))
self.assertFalse(
volume.intersects(
QgsOrientedBox3D.fromBox3D(QgsBox3d(11, 2, 3, 10, 11, 12))
)
)
self.assertTrue(
volume.intersects(
QgsOrientedBox3D.fromBox3D(QgsBox3d(6.5, 2, 3, 10, 11, 12))
)
)
if __name__ == "__main__":
unittest.main()

View File

@ -16,10 +16,11 @@ import unittest
import qgis # NOQA
from qgis.core import (
Qgis,
QgsTiledSceneBoundingVolumeRegion,
QgsTiledSceneBoundingVolume,
QgsBox3d,
QgsMatrix4x4,
QgsTiledSceneTile,
QgsOrientedBox3D
)
from qgis.testing import start_app, QgisTestCase
@ -45,9 +46,9 @@ class TestQgsTiledSceneTile(QgisTestCase):
node = QgsTiledSceneTile()
node.setBoundingVolume(
QgsTiledSceneBoundingVolumeRegion(QgsBox3d(1, 2, 3, 10, 11, 12))
QgsTiledSceneBoundingVolume(QgsOrientedBox3D.fromBox3D(QgsBox3d(1, 2, 3, 10, 11, 12)))
)
self.assertEqual(node.boundingVolume().region(), QgsBox3d(1, 2, 3, 10, 11, 12))
self.assertEqual(node.boundingVolume().box(), QgsOrientedBox3D([5.5, 6.5, 7.5], [4.5, 0, 0, 0, 4.5, 0, 0, 0, 4.5]))
node = QgsTiledSceneTile()
node.setTransform(QgsMatrix4x4(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0))
@ -68,7 +69,7 @@ class TestQgsTiledSceneTile(QgisTestCase):
node = QgsTiledSceneTile(11)
node.setRefinementProcess(Qgis.TileRefinementProcess.Additive)
node.setBoundingVolume(
QgsTiledSceneBoundingVolumeRegion(QgsBox3d(1, 2, 3, 10, 11, 12))
QgsTiledSceneBoundingVolume(QgsOrientedBox3D.fromBox3D(QgsBox3d(1, 2, 3, 10, 11, 12)))
)
node.setTransform(QgsMatrix4x4(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0))
node.setResources({"content": "parent"})
@ -78,7 +79,7 @@ class TestQgsTiledSceneTile(QgisTestCase):
self.assertTrue(copy.isValid())
self.assertEqual(copy.id(), 11)
self.assertEqual(copy.refinementProcess(), Qgis.TileRefinementProcess.Additive)
self.assertEqual(copy.boundingVolume().region(), QgsBox3d(1, 2, 3, 10, 11, 12))
self.assertEqual(copy.boundingVolume().box(), QgsOrientedBox3D([5.5, 6.5, 7.5], [4.5, 0, 0, 0, 4.5, 0, 0, 0, 4.5]))
self.assertEqual(
copy.transform(),
QgsMatrix4x4(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0),
@ -90,7 +91,7 @@ class TestQgsTiledSceneTile(QgisTestCase):
node = QgsTiledSceneTile()
self.assertIsNone(node.transform())
node.setBoundingVolume(
QgsTiledSceneBoundingVolumeRegion(QgsBox3d(1, 2, 3, 10, 11, 12))
QgsTiledSceneBoundingVolume(QgsOrientedBox3D.fromBox3D(QgsBox3d(1, 2, 3, 10, 11, 12)))
)
node.setTransform(QgsMatrix4x4(1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0))