mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-09 00:08:52 -04:00
570 lines
19 KiB
C++
570 lines
19 KiB
C++
/***************************************************************************
|
|
qgs3dutils.cpp
|
|
--------------------------------------
|
|
Date : July 2017
|
|
Copyright : (C) 2017 by Martin Dobias
|
|
Email : wonder dot sk at gmail dot com
|
|
***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgs3dutils.h"
|
|
|
|
#include "qgslinestring.h"
|
|
#include "qgspolygon.h"
|
|
#include "qgsfeaturerequest.h"
|
|
#include "qgsfeatureiterator.h"
|
|
#include "qgsfeature.h"
|
|
#include "qgsabstractgeometry.h"
|
|
#include "qgsvectorlayer.h"
|
|
#include "qgsexpressioncontextutils.h"
|
|
#include "qgsfeedback.h"
|
|
#include "qgsexpression.h"
|
|
#include "qgsexpressionutils.h"
|
|
#include "qgsoffscreen3dengine.h"
|
|
|
|
#include "qgs3dmapscene.h"
|
|
#include "qgsabstract3dengine.h"
|
|
#include "qgsterraingenerator.h"
|
|
#include "qgscameracontroller.h"
|
|
|
|
#include "qgsline3dsymbol.h"
|
|
#include "qgspoint3dsymbol.h"
|
|
#include "qgspolygon3dsymbol.h"
|
|
//#include "qgspointcloud3dsymbol.h"
|
|
|
|
#include <Qt3DExtras/QPhongMaterial>
|
|
|
|
QImage Qgs3DUtils::captureSceneImage( QgsAbstract3DEngine &engine, Qgs3DMapScene *scene )
|
|
{
|
|
QImage resImage;
|
|
QEventLoop evLoop;
|
|
|
|
auto requestImageFcn = [&engine, scene]
|
|
{
|
|
if ( scene->sceneState() == Qgs3DMapScene::Ready )
|
|
{
|
|
engine.requestCaptureImage();
|
|
}
|
|
};
|
|
|
|
auto saveImageFcn = [&evLoop, &resImage]( const QImage & img )
|
|
{
|
|
resImage = img;
|
|
evLoop.quit();
|
|
};
|
|
|
|
QMetaObject::Connection conn1 = QObject::connect( &engine, &QgsAbstract3DEngine::imageCaptured, saveImageFcn );
|
|
QMetaObject::Connection conn2;
|
|
|
|
if ( scene->sceneState() == Qgs3DMapScene::Ready )
|
|
{
|
|
requestImageFcn();
|
|
}
|
|
else
|
|
{
|
|
// first wait until scene is loaded
|
|
conn2 = QObject::connect( scene, &Qgs3DMapScene::sceneStateChanged, requestImageFcn );
|
|
}
|
|
|
|
evLoop.exec();
|
|
|
|
QObject::disconnect( conn1 );
|
|
if ( conn2 )
|
|
QObject::disconnect( conn2 );
|
|
|
|
return resImage;
|
|
}
|
|
|
|
bool Qgs3DUtils::exportAnimation( const Qgs3DAnimationSettings &animationSettings,
|
|
const Qgs3DMapSettings &mapSettings,
|
|
int framesPerSecond,
|
|
const QString &outputDirectory,
|
|
const QString &fileNameTemplate,
|
|
const QSize &outputSize,
|
|
QString &error,
|
|
QgsFeedback *feedback
|
|
)
|
|
{
|
|
QgsOffscreen3DEngine engine;
|
|
engine.setSize( outputSize );
|
|
Qgs3DMapScene *scene = new Qgs3DMapScene( mapSettings, &engine );
|
|
engine.setRootEntity( scene );
|
|
|
|
if ( animationSettings.keyFrames().size() < 2 )
|
|
{
|
|
error = QObject::tr( "Unable to export 3D animation. Add at least 2 keyframes" );
|
|
return false;
|
|
}
|
|
|
|
const float duration = animationSettings.duration(); //in seconds
|
|
if ( duration <= 0 )
|
|
{
|
|
error = QObject::tr( "Unable to export 3D animation (invalid duration)." );
|
|
return false;
|
|
}
|
|
|
|
float time = 0;
|
|
int frameNo = 0;
|
|
int totalFrames = static_cast<int>( duration * framesPerSecond );
|
|
|
|
if ( fileNameTemplate.isEmpty() )
|
|
{
|
|
error = QObject::tr( "Filename template is empty" );
|
|
return false;
|
|
}
|
|
|
|
int numberOfDigits = fileNameTemplate.count( QLatin1Char( '#' ) );
|
|
if ( numberOfDigits < 0 )
|
|
{
|
|
error = QObject::tr( "Wrong filename template format (must contain #)" );
|
|
return false;
|
|
}
|
|
const QString token( numberOfDigits, QLatin1Char( '#' ) );
|
|
if ( !fileNameTemplate.contains( token ) )
|
|
{
|
|
error = QObject::tr( "Filename template must contain all # placeholders in one continuous group." );
|
|
return false;
|
|
}
|
|
|
|
while ( time <= duration )
|
|
{
|
|
|
|
if ( feedback )
|
|
{
|
|
if ( feedback->isCanceled() )
|
|
{
|
|
error = QObject::tr( "Export canceled" );
|
|
return false;
|
|
}
|
|
feedback->setProgress( frameNo / static_cast<double>( totalFrames ) * 100 );
|
|
}
|
|
++frameNo;
|
|
|
|
Qgs3DAnimationSettings::Keyframe kf = animationSettings.interpolate( time );
|
|
scene->cameraController()->setLookingAtPoint( kf.point, kf.dist, kf.pitch, kf.yaw );
|
|
|
|
QString fileName( fileNameTemplate );
|
|
const QString frameNoPaddedLeft( QStringLiteral( "%1" ).arg( frameNo, numberOfDigits, 10, QChar( '0' ) ) ); // e.g. 0001
|
|
fileName.replace( token, frameNoPaddedLeft );
|
|
const QString path = QDir( outputDirectory ).filePath( fileName );
|
|
|
|
// It would initially return empty rendered image.
|
|
// Capturing the initial image and throwing it away fixes that.
|
|
// Hopefully we will find a better fix in the future.
|
|
Qgs3DUtils::captureSceneImage( engine, scene );
|
|
QImage img = Qgs3DUtils::captureSceneImage( engine, scene );
|
|
|
|
img.save( path );
|
|
|
|
time += 1.0f / static_cast<float>( framesPerSecond );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
int Qgs3DUtils::maxZoomLevel( double tile0width, double tileResolution, double maxError )
|
|
{
|
|
if ( maxError <= 0 || tileResolution <= 0 || tile0width <= 0 )
|
|
return 0; // invalid input
|
|
|
|
// derived from:
|
|
// tile width [map units] = tile0width / 2^zoomlevel
|
|
// tile error [map units] = tile width / tile resolution
|
|
// + re-arranging to get zoom level if we know tile error we want to get
|
|
double zoomLevel = -log( tileResolution * maxError / tile0width ) / log( 2 );
|
|
return round( zoomLevel ); // we could use ceil() here if we wanted to always get to the desired error
|
|
}
|
|
|
|
QString Qgs3DUtils::altClampingToString( Qgs3DTypes::AltitudeClamping altClamp )
|
|
{
|
|
switch ( altClamp )
|
|
{
|
|
case Qgs3DTypes::AltClampAbsolute: return QStringLiteral( "absolute" );
|
|
case Qgs3DTypes::AltClampRelative: return QStringLiteral( "relative" );
|
|
case Qgs3DTypes::AltClampTerrain: return QStringLiteral( "terrain" );
|
|
default: Q_ASSERT( false ); return QString();
|
|
}
|
|
}
|
|
|
|
|
|
Qgs3DTypes::AltitudeClamping Qgs3DUtils::altClampingFromString( const QString &str )
|
|
{
|
|
if ( str == QLatin1String( "absolute" ) )
|
|
return Qgs3DTypes::AltClampAbsolute;
|
|
else if ( str == QLatin1String( "terrain" ) )
|
|
return Qgs3DTypes::AltClampTerrain;
|
|
else // "relative" (default)
|
|
return Qgs3DTypes::AltClampRelative;
|
|
}
|
|
|
|
|
|
QString Qgs3DUtils::altBindingToString( Qgs3DTypes::AltitudeBinding altBind )
|
|
{
|
|
switch ( altBind )
|
|
{
|
|
case Qgs3DTypes::AltBindVertex: return QStringLiteral( "vertex" );
|
|
case Qgs3DTypes::AltBindCentroid: return QStringLiteral( "centroid" );
|
|
default: Q_ASSERT( false ); return QString();
|
|
}
|
|
}
|
|
|
|
|
|
Qgs3DTypes::AltitudeBinding Qgs3DUtils::altBindingFromString( const QString &str )
|
|
{
|
|
if ( str == QLatin1String( "vertex" ) )
|
|
return Qgs3DTypes::AltBindVertex;
|
|
else // "centroid" (default)
|
|
return Qgs3DTypes::AltBindCentroid;
|
|
}
|
|
|
|
QString Qgs3DUtils::cullingModeToString( Qgs3DTypes::CullingMode mode )
|
|
{
|
|
switch ( mode )
|
|
{
|
|
case Qgs3DTypes::NoCulling: return QStringLiteral( "no-culling" );
|
|
case Qgs3DTypes::Front: return QStringLiteral( "front" );
|
|
case Qgs3DTypes::Back: return QStringLiteral( "back" );
|
|
case Qgs3DTypes::FrontAndBack: return QStringLiteral( "front-and-back" );
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
Qgs3DTypes::CullingMode Qgs3DUtils::cullingModeFromString( const QString &str )
|
|
{
|
|
if ( str == QLatin1String( "front" ) )
|
|
return Qgs3DTypes::Front;
|
|
else if ( str == QLatin1String( "back" ) )
|
|
return Qgs3DTypes::Back;
|
|
else if ( str == QLatin1String( "front-and-back" ) )
|
|
return Qgs3DTypes::FrontAndBack;
|
|
else
|
|
return Qgs3DTypes::NoCulling;
|
|
}
|
|
|
|
float Qgs3DUtils::clampAltitude( const QgsPoint &p, Qgs3DTypes::AltitudeClamping altClamp, Qgs3DTypes::AltitudeBinding altBind, float height, const QgsPoint ¢roid, const Qgs3DMapSettings &map )
|
|
{
|
|
float terrainZ = 0;
|
|
if ( altClamp == Qgs3DTypes::AltClampRelative || altClamp == Qgs3DTypes::AltClampTerrain )
|
|
{
|
|
QgsPointXY pt = altBind == Qgs3DTypes::AltBindVertex ? p : centroid;
|
|
terrainZ = map.terrainGenerator()->heightAt( pt.x(), pt.y(), map );
|
|
}
|
|
|
|
float geomZ = 0;
|
|
if ( p.is3D() && ( altClamp == Qgs3DTypes::AltClampAbsolute || altClamp == Qgs3DTypes::AltClampRelative ) )
|
|
geomZ = p.z();
|
|
|
|
float z = ( terrainZ + geomZ ) * map.terrainVerticalScale() + height;
|
|
return z;
|
|
}
|
|
|
|
void Qgs3DUtils::clampAltitudes( QgsLineString *lineString, Qgs3DTypes::AltitudeClamping altClamp, Qgs3DTypes::AltitudeBinding altBind, const QgsPoint ¢roid, float height, const Qgs3DMapSettings &map )
|
|
{
|
|
for ( int i = 0; i < lineString->nCoordinates(); ++i )
|
|
{
|
|
float terrainZ = 0;
|
|
if ( altClamp == Qgs3DTypes::AltClampRelative || altClamp == Qgs3DTypes::AltClampTerrain )
|
|
{
|
|
QgsPointXY pt;
|
|
if ( altBind == Qgs3DTypes::AltBindVertex )
|
|
{
|
|
pt.setX( lineString->xAt( i ) );
|
|
pt.setY( lineString->yAt( i ) );
|
|
}
|
|
else
|
|
{
|
|
pt.set( centroid.x(), centroid.y() );
|
|
}
|
|
terrainZ = map.terrainGenerator()->heightAt( pt.x(), pt.y(), map );
|
|
}
|
|
|
|
float geomZ = 0;
|
|
if ( altClamp == Qgs3DTypes::AltClampAbsolute || altClamp == Qgs3DTypes::AltClampRelative )
|
|
geomZ = lineString->zAt( i );
|
|
|
|
float z = ( terrainZ + geomZ ) * map.terrainVerticalScale() + height;
|
|
lineString->setZAt( i, z );
|
|
}
|
|
}
|
|
|
|
|
|
bool Qgs3DUtils::clampAltitudes( QgsPolygon *polygon, Qgs3DTypes::AltitudeClamping altClamp, Qgs3DTypes::AltitudeBinding altBind, float height, const Qgs3DMapSettings &map )
|
|
{
|
|
if ( !polygon->is3D() )
|
|
polygon->addZValue( 0 );
|
|
|
|
QgsPoint centroid;
|
|
if ( altBind == Qgs3DTypes::AltBindCentroid )
|
|
centroid = polygon->centroid();
|
|
|
|
QgsCurve *curve = const_cast<QgsCurve *>( polygon->exteriorRing() );
|
|
QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
|
|
if ( !lineString )
|
|
return false;
|
|
|
|
clampAltitudes( lineString, altClamp, altBind, centroid, height, map );
|
|
|
|
for ( int i = 0; i < polygon->numInteriorRings(); ++i )
|
|
{
|
|
QgsCurve *curve = const_cast<QgsCurve *>( polygon->interiorRing( i ) );
|
|
QgsLineString *lineString = qgsgeometry_cast<QgsLineString *>( curve );
|
|
if ( !lineString )
|
|
return false;
|
|
|
|
clampAltitudes( lineString, altClamp, altBind, centroid, height, map );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
QString Qgs3DUtils::matrix4x4toString( const QMatrix4x4 &m )
|
|
{
|
|
const float *d = m.constData();
|
|
QStringList elems;
|
|
elems.reserve( 16 );
|
|
for ( int i = 0; i < 16; ++i )
|
|
elems << QString::number( d[i] );
|
|
return elems.join( ' ' );
|
|
}
|
|
|
|
QMatrix4x4 Qgs3DUtils::stringToMatrix4x4( const QString &str )
|
|
{
|
|
QMatrix4x4 m;
|
|
float *d = m.data();
|
|
QStringList elems = str.split( ' ' );
|
|
for ( int i = 0; i < 16; ++i )
|
|
d[i] = elems[i].toFloat();
|
|
return m;
|
|
}
|
|
|
|
void Qgs3DUtils::extractPointPositions( const QgsFeature &f, const Qgs3DMapSettings &map, Qgs3DTypes::AltitudeClamping altClamp, QVector<QVector3D> &positions )
|
|
{
|
|
const QgsAbstractGeometry *g = f.geometry().constGet();
|
|
for ( auto it = g->vertices_begin(); it != g->vertices_end(); ++it )
|
|
{
|
|
QgsPoint pt = *it;
|
|
float geomZ = 0;
|
|
if ( pt.is3D() )
|
|
{
|
|
geomZ = pt.z();
|
|
}
|
|
float terrainZ = map.terrainGenerator()->heightAt( pt.x(), pt.y(), map ) * map.terrainVerticalScale();
|
|
float h;
|
|
switch ( altClamp )
|
|
{
|
|
case Qgs3DTypes::AltClampAbsolute:
|
|
default:
|
|
h = geomZ;
|
|
break;
|
|
case Qgs3DTypes::AltClampTerrain:
|
|
h = terrainZ;
|
|
break;
|
|
case Qgs3DTypes::AltClampRelative:
|
|
h = terrainZ + geomZ;
|
|
break;
|
|
}
|
|
positions.append( QVector3D( pt.x() - map.origin().x(), h, -( pt.y() - map.origin().y() ) ) );
|
|
//qDebug() << positions.last();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* copied from https://searchcode.com/codesearch/view/35195518/
|
|
* qt3d /src/threed/painting/qglpainter.cpp
|
|
* no changes in the code
|
|
*/
|
|
static inline uint outcode( QVector4D v )
|
|
{
|
|
// For a discussion of outcodes see pg 388 Dunn & Parberry.
|
|
// For why you can't just test if the point is in a bounding box
|
|
// consider the case where a view frustum with view-size 1.5 x 1.5
|
|
// is tested against a 2x2 box which encloses the near-plane, while
|
|
// all the points in the box are outside the frustum.
|
|
// TODO: optimise this with assembler - according to D&P this can
|
|
// be done in one line of assembler on some platforms
|
|
uint code = 0;
|
|
if ( v.x() < -v.w() ) code |= 0x01;
|
|
if ( v.x() > v.w() ) code |= 0x02;
|
|
if ( v.y() < -v.w() ) code |= 0x04;
|
|
if ( v.y() > v.w() ) code |= 0x08;
|
|
if ( v.z() < -v.w() ) code |= 0x10;
|
|
if ( v.z() > v.w() ) code |= 0x20;
|
|
return code;
|
|
}
|
|
|
|
|
|
/**
|
|
* coarse box vs frustum test for culling.
|
|
* corners of oriented box are transformed to clip space
|
|
* and there is a test that all points are on the wrong side of the same plane
|
|
* see http://www.lighthouse3d.com/tutorials/view-frustum-culling/geometric-approach-testing-boxes/
|
|
*
|
|
* should be equivalent to https://searchcode.com/codesearch/view/35195518/
|
|
* qt3d /src/threed/painting/qglpainter.cpp
|
|
* bool QGLPainter::isCullable(const QBox3D& box) const
|
|
*/
|
|
bool Qgs3DUtils::isCullable( const QgsAABB &bbox, const QMatrix4x4 &viewProjectionMatrix )
|
|
{
|
|
uint out = 0xff;
|
|
|
|
for ( int i = 0; i < 8; ++i )
|
|
{
|
|
QVector4D p( ( ( i >> 0 ) & 1 ) ? bbox.xMin : bbox.xMax,
|
|
( ( i >> 1 ) & 1 ) ? bbox.yMin : bbox.yMax,
|
|
( ( i >> 2 ) & 1 ) ? bbox.zMin : bbox.zMax, 1 );
|
|
QVector4D pc = viewProjectionMatrix * p;
|
|
|
|
// if the logical AND of all the outcodes is non-zero then the BB is
|
|
// definitely outside the view frustum.
|
|
out = out & outcode( pc );
|
|
}
|
|
return out;
|
|
}
|
|
|
|
QgsVector3D Qgs3DUtils::mapToWorldCoordinates( const QgsVector3D &mapCoords, const QgsVector3D &origin )
|
|
{
|
|
return QgsVector3D( mapCoords.x() - origin.x(),
|
|
mapCoords.z() - origin.z(),
|
|
-( mapCoords.y() - origin.y() ) );
|
|
|
|
}
|
|
|
|
QgsVector3D Qgs3DUtils::worldToMapCoordinates( const QgsVector3D &worldCoords, const QgsVector3D &origin )
|
|
{
|
|
return QgsVector3D( worldCoords.x() + origin.x(),
|
|
-worldCoords.z() + origin.y(),
|
|
worldCoords.y() + origin.z() );
|
|
}
|
|
|
|
static QgsRectangle _tryReprojectExtent2D( const QgsRectangle &extent, const QgsCoordinateReferenceSystem &crs1, const QgsCoordinateReferenceSystem &crs2, const QgsCoordinateTransformContext &context )
|
|
{
|
|
QgsRectangle extentMapCrs( extent );
|
|
if ( crs1 != crs2 )
|
|
{
|
|
// reproject if necessary
|
|
QgsCoordinateTransform ct( crs1, crs2, context );
|
|
try
|
|
{
|
|
extentMapCrs = ct.transformBoundingBox( extentMapCrs );
|
|
}
|
|
catch ( const QgsCsException & )
|
|
{
|
|
// bad luck, can't reproject for some reason
|
|
QgsDebugMsg( QStringLiteral( "3D utils: transformation of extent failed: " ) + extentMapCrs.toString( -1 ) );
|
|
}
|
|
}
|
|
return extentMapCrs;
|
|
}
|
|
|
|
QgsAABB Qgs3DUtils::layerToWorldExtent( const QgsRectangle &extent, double zMin, double zMax, const QgsCoordinateReferenceSystem &layerCrs, const QgsVector3D &mapOrigin, const QgsCoordinateReferenceSystem &mapCrs, const QgsCoordinateTransformContext &context )
|
|
{
|
|
QgsRectangle extentMapCrs( _tryReprojectExtent2D( extent, layerCrs, mapCrs, context ) );
|
|
return mapToWorldExtent( extentMapCrs, zMin, zMax, mapOrigin );
|
|
}
|
|
|
|
QgsRectangle Qgs3DUtils::worldToLayerExtent( const QgsAABB &bbox, const QgsCoordinateReferenceSystem &layerCrs, const QgsVector3D &mapOrigin, const QgsCoordinateReferenceSystem &mapCrs, const QgsCoordinateTransformContext &context )
|
|
{
|
|
QgsRectangle extentMap = worldToMapExtent( bbox, mapOrigin );
|
|
return _tryReprojectExtent2D( extentMap, mapCrs, layerCrs, context );
|
|
}
|
|
|
|
QgsAABB Qgs3DUtils::mapToWorldExtent( const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin )
|
|
{
|
|
QgsVector3D extentMin3D( extent.xMinimum(), extent.yMinimum(), zMin );
|
|
QgsVector3D extentMax3D( extent.xMaximum(), extent.yMaximum(), zMax );
|
|
QgsVector3D worldExtentMin3D = mapToWorldCoordinates( extentMin3D, mapOrigin );
|
|
QgsVector3D worldExtentMax3D = mapToWorldCoordinates( extentMax3D, mapOrigin );
|
|
QgsAABB rootBbox( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMin3D.z(),
|
|
worldExtentMax3D.x(), worldExtentMax3D.y(), worldExtentMax3D.z() );
|
|
return rootBbox;
|
|
}
|
|
|
|
QgsRectangle Qgs3DUtils::worldToMapExtent( const QgsAABB &bbox, const QgsVector3D &mapOrigin )
|
|
{
|
|
QgsVector3D worldExtentMin3D = Qgs3DUtils::worldToMapCoordinates( QgsVector3D( bbox.xMin, bbox.yMin, bbox.zMin ), mapOrigin );
|
|
QgsVector3D worldExtentMax3D = Qgs3DUtils::worldToMapCoordinates( QgsVector3D( bbox.xMax, bbox.yMax, bbox.zMax ), mapOrigin );
|
|
QgsRectangle extentMap( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMax3D.x(), worldExtentMax3D.y() );
|
|
// we discard zMin/zMax here because we don't need it
|
|
return extentMap;
|
|
}
|
|
|
|
|
|
QgsVector3D Qgs3DUtils::transformWorldCoordinates( const QgsVector3D &worldPoint1, const QgsVector3D &origin1, const QgsCoordinateReferenceSystem &crs1, const QgsVector3D &origin2, const QgsCoordinateReferenceSystem &crs2, const QgsCoordinateTransformContext &context )
|
|
{
|
|
QgsVector3D mapPoint1 = worldToMapCoordinates( worldPoint1, origin1 );
|
|
QgsVector3D mapPoint2 = mapPoint1;
|
|
if ( crs1 != crs2 )
|
|
{
|
|
// reproject if necessary
|
|
QgsCoordinateTransform ct( crs1, crs2, context );
|
|
try
|
|
{
|
|
QgsPointXY pt = ct.transform( QgsPointXY( mapPoint1.x(), mapPoint1.y() ) );
|
|
mapPoint2.set( pt.x(), pt.y(), mapPoint1.z() );
|
|
}
|
|
catch ( const QgsCsException & )
|
|
{
|
|
// bad luck, can't reproject for some reason
|
|
}
|
|
}
|
|
return mapToWorldCoordinates( mapPoint2, origin2 );
|
|
}
|
|
|
|
void Qgs3DUtils::estimateVectorLayerZRange( QgsVectorLayer *layer, double &zMin, double &zMax )
|
|
{
|
|
if ( !QgsWkbTypes::hasZ( layer->wkbType() ) )
|
|
{
|
|
zMin = 0;
|
|
zMax = 0;
|
|
return;
|
|
}
|
|
|
|
zMin = std::numeric_limits<double>::max();
|
|
zMax = std::numeric_limits<double>::min();
|
|
|
|
QgsFeature f;
|
|
QgsFeatureIterator it = layer->getFeatures( QgsFeatureRequest().setNoAttributes().setLimit( 100 ) );
|
|
while ( it.nextFeature( f ) )
|
|
{
|
|
QgsGeometry g = f.geometry();
|
|
for ( auto vit = g.vertices_begin(); vit != g.vertices_end(); ++vit )
|
|
{
|
|
double z = ( *vit ).z();
|
|
if ( z < zMin ) zMin = z;
|
|
if ( z > zMax ) zMax = z;
|
|
}
|
|
}
|
|
|
|
if ( zMin == std::numeric_limits<double>::max() && zMax == std::numeric_limits<double>::min() )
|
|
{
|
|
zMin = 0;
|
|
zMax = 0;
|
|
}
|
|
}
|
|
|
|
QgsExpressionContext Qgs3DUtils::globalProjectLayerExpressionContext( QgsVectorLayer *layer )
|
|
{
|
|
QgsExpressionContext exprContext;
|
|
exprContext << QgsExpressionContextUtils::globalScope()
|
|
<< QgsExpressionContextUtils::projectScope( QgsProject::instance() )
|
|
<< QgsExpressionContextUtils::layerScope( layer );
|
|
return exprContext;
|
|
}
|
|
|
|
QgsPhongMaterialSettings Qgs3DUtils::phongMaterialFromQt3DComponent( Qt3DExtras::QPhongMaterial *material )
|
|
{
|
|
QgsPhongMaterialSettings settings;
|
|
settings.setAmbient( material->ambient() );
|
|
settings.setDiffuse( material->diffuse() );
|
|
settings.setSpecular( material->specular() );
|
|
settings.setShininess( material->shininess() );
|
|
return settings;
|
|
}
|