Skip to content

Commit

Permalink
Make tessellator not flip axes and use the new option in QGIS code
Browse files Browse the repository at this point in the history
We add a new setOutputZUp(true) option to QgsTessellator so that
the coordinates on output do not get flipped from [x,y,z] to [x,-z,y].

The default is still the old behavior with flipped axes (to make sure
we do not break the API), but all uses in the QGIS code were upgraded
to use the new option.

This means we do not need to add rotation to polygon / buffered line
symbol's 3D entities in 3D map views (and there's only translation left, yay!)
  • Loading branch information
wonder-sk committed Nov 6, 2024
1 parent 5fad1e1 commit 4b3c54d
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 38 deletions.
18 changes: 18 additions & 0 deletions python/PyQt6/core/auto_generated/qgstessellator.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ stability during calculations.
If ``noZ`` is ``True``, then a 2-dimensional tessellation only will be performed and all z coordinates will be ignored.

.. versionadded:: 3.10
%End

void setOutputZUp( bool zUp );
%Docstring
Sets whether the "up" direction should be the Z axis on output (true),
otherwise the "up" direction will be the Y axis (false). The default
value is false (to keep compatibility for existing tessellator use cases).

.. versionadded:: 3.42
%End

bool isOutputZUp() const;
%Docstring
Returns whether the "up" direction should be the Z axis on output (true),
otherwise the "up" direction will be the Y axis (false). The default
value is false (to keep compatibility for existing tessellator use cases).

.. versionadded:: 3.42
%End

void addPolygon( const QgsPolygon &polygon, float extrusionHeight );
Expand Down
18 changes: 18 additions & 0 deletions python/core/auto_generated/qgstessellator.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ stability during calculations.
If ``noZ`` is ``True``, then a 2-dimensional tessellation only will be performed and all z coordinates will be ignored.

.. versionadded:: 3.10
%End

void setOutputZUp( bool zUp );
%Docstring
Sets whether the "up" direction should be the Z axis on output (true),
otherwise the "up" direction will be the Y axis (false). The default
value is false (to keep compatibility for existing tessellator use cases).

.. versionadded:: 3.42
%End

bool isOutputZUp() const;
%Docstring
Returns whether the "up" direction should be the Z axis on output (true),
otherwise the "up" direction will be the Y axis (false). The default
value is false (to keep compatibility for existing tessellator use cases).

.. versionadded:: 3.42
%End

void addPolygon( const QgsPolygon &polygon, float extrusionHeight );
Expand Down
1 change: 1 addition & 0 deletions src/3d/processing/qgsalgorithmtessellate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ QgsFeatureList QgsTessellateAlgorithm::processFeature( const QgsFeature &feature
// 3D case, or 2D case with unsupported GEOS version -- use less stable poly2tri backend
const QgsRectangle bounds = f.geometry().boundingBox();
QgsTessellator t( bounds, false );
t.setOutputZUp( true );

if ( f.geometry().isMultipart() )
{
Expand Down
4 changes: 3 additions & 1 deletion src/3d/symbols/qgsline3dsymbol_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ bool QgsBufferedLine3DSymbolHandler::prepare( const Qgs3DRenderContext &, QSet<Q
3,
texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );

outNormal.tessellator->setOutputZUp( true );
outSelected.tessellator->setOutputZUp( true );

return true;
}

Expand Down Expand Up @@ -225,7 +228,6 @@ void QgsBufferedLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, cons

// add transform (our geometry has coordinates relative to mChunkOrigin)
Qt3DCore::QTransform *tr = new Qt3DCore::QTransform;
tr->setRotation( QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), 90 ) ); // flip (x,z,-y) to map (x,y,z)
QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D();
tr->setTranslation( nodeTranslation );

Expand Down
4 changes: 3 additions & 1 deletion src/3d/symbols/qgspolygon3dsymbol_p.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet
mSymbol->renderedFacade(),
texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) );

outNormal.tessellator->setOutputZUp( true );
outSelected.tessellator->setOutputZUp( true );

QSet<QString> attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() );
attributeNames.unite( attrs );
attrs = mSymbol->materialSettings()->dataDefinedProperties().referencedFields( context.expressionContext() );
Expand Down Expand Up @@ -298,7 +301,6 @@ void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs

// add transform (our geometry has coordinates relative to mChunkOrigin)
Qt3DCore::QTransform *tr = new Qt3DCore::QTransform;
tr->setRotation( QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), 90 ) ); // flip (x,z,-y) to map (x,y,z)
QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D();
tr->setTranslation( nodeTranslation );

Expand Down
7 changes: 4 additions & 3 deletions src/core/geometry/qgsinternalgeometryengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,7 @@ QVector<QgsPointXY> randomPointsInPolygonPoly2TriBackend( const QgsAbstractGeome
// step 1 - tessellate the polygon to triangles
QgsRectangle bounds = geometry->boundingBox();
QgsTessellator t( bounds, false, false, false, true );
t.setOutputZUp( true );

if ( const QgsMultiSurface *ms = qgsgeometry_cast< const QgsMultiSurface * >( geometry ) )
{
Expand Down Expand Up @@ -1285,14 +1286,14 @@ QVector<QgsPointXY> randomPointsInPolygonPoly2TriBackend( const QgsAbstractGeome
return QVector< QgsPointXY >();

const float aX = *it++;
const float aY = *it++;
( void )it++; // z
const float aY = -( *it++ );
const float bX = *it++;
const float bY = *it++;
( void )it++; // z
const float bY = -( *it++ );
const float cX = *it++;
const float cY = *it++;
( void )it++; // z
const float cY = -( *it++ );

const double area = QgsGeometryUtilsBase::triangleArea( aX, aY, bX, bY, cX, cY );
totalArea += area;
Expand Down
130 changes: 99 additions & 31 deletions src/core/qgstessellator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,13 @@ static std::pair<float, float> rotateCoords( float x, float y, float origin_x, f
return std::make_pair( x1, y1 );
}

static void make_quad( float x0, float y0, float z0, float x1, float y1, float z1, float height, QVector<float> &data, bool addNormals, bool addTextureCoords, float textureRotation )
static void make_quad( float x0, float y0, float z0, float x1, float y1, float z1, float height, QVector<float> &data, bool addNormals, bool addTextureCoords, float textureRotation, bool zUp )
{
const float dx = x1 - x0;
const float dy = -( y1 - y0 );
const float dy = y1 - y0;

// perpendicular vector in plane to [x,y] is [-y,x]
QVector3D vn( -dy, 0, dx );
vn = -vn;
QVector3D vn = zUp ? QVector3D( -dy, dx, 0 ) : QVector3D( -dy, 0, -dx );
vn.normalize();

float u0, v0;
Expand Down Expand Up @@ -119,39 +118,57 @@ static void make_quad( float x0, float y0, float z0, float x1, float y1, float z

// triangle 1
// vertice 1
data << x0 << z0 + height << -y0;
if ( zUp )
data << x0 << y0 << z0 + height;
else
data << x0 << z0 + height << -y0;
if ( addNormals )
data << vn.x() << vn.y() << vn.z();
if ( addTextureCoords )
data << textureCoordinates[0] << textureCoordinates[1];
// vertice 2
data << x1 << z1 + height << -y1;
if ( zUp )
data << x1 << y1 << z1 + height;
else
data << x1 << z1 + height << -y1;
if ( addNormals )
data << vn.x() << vn.y() << vn.z();
if ( addTextureCoords )
data << textureCoordinates[2] << textureCoordinates[3];
// verice 3
data << x0 << z0 << -y0;
if ( zUp )
data << x0 << y0 << z0;
else
data << x0 << z0 << -y0;
if ( addNormals )
data << vn.x() << vn.y() << vn.z();
if ( addTextureCoords )
data << textureCoordinates[4] << textureCoordinates[5];

// triangle 2
// vertice 1
data << x0 << z0 << -y0;
if ( zUp )
data << x0 << y0 << z0;
else
data << x0 << z0 << -y0;
if ( addNormals )
data << vn.x() << vn.y() << vn.z();
if ( addTextureCoords )
data << textureCoordinates[6] << textureCoordinates[7];
// vertice 2
data << x1 << z1 + height << -y1;
if ( zUp )
data << x1 << y1 << z1 + height;
else
data << x1 << z1 + height << -y1;
if ( addNormals )
data << vn.x() << vn.y() << vn.z();
if ( addTextureCoords )
data << textureCoordinates[8] << textureCoordinates[9];
// vertice 3
data << x1 << z1 << -y1;
if ( zUp )
data << x1 << y1 << z1;
else
data << x1 << z1 << -y1;
if ( addNormals )
data << vn.x() << vn.y() << vn.z();
if ( addTextureCoords )
Expand Down Expand Up @@ -216,7 +233,7 @@ static bool _isRingCounterClockWise( const QgsCurve &ring )
}

static void _makeWalls( const QgsLineString &ring, bool ccw, float extrusionHeight, QVector<float> &data,
bool addNormals, bool addTextureCoords, double originX, double originY, float textureRotation )
bool addNormals, bool addTextureCoords, double originX, double originY, float textureRotation, bool zUp )
{
// we need to find out orientation of the ring so that the triangles we generate
// face the right direction
Expand All @@ -234,7 +251,7 @@ static void _makeWalls( const QgsLineString &ring, bool ccw, float extrusionHeig
const float z1 = std::isnan( pt.z() ) ? 0 : pt.z();

// make a quad
make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals, addTextureCoords, textureRotation );
make_quad( x0, y0, z0, x1, y1, z1, extrusionHeight, data, addNormals, addTextureCoords, textureRotation, zUp );
ptPrev = pt;
}
}
Expand Down Expand Up @@ -554,9 +571,19 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
if ( z > zMaxExtruded )
zMaxExtruded = z;

mData << *xData - mOriginX << z << - *yData + mOriginY;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
if ( mOutputZUp )
{
mData << *xData - mOriginX << *yData - mOriginY << z;
if ( mAddNormals )
mData << pNormal.x() << pNormal.y() << pNormal.z();
}
else // Y axis is the up direction
{
mData << *xData - mOriginX << z << - *yData + mOriginY;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
}

if ( mAddTextureCoords )
{
std::pair<float, float> p( triangle->xAt( i ), triangle->yAt( i ) );
Expand All @@ -581,9 +608,19 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
// the same triangle with reversed order of coordinates and inverted normal
for ( int i = 2; i >= 0; i-- )
{
mData << exterior->xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->zAt( i ) ) << - exterior->yAt( i ) + mOriginY;
if ( mAddNormals )
mData << -pNormal.x() << -pNormal.z() << pNormal.y();
if ( mOutputZUp )
{
mData << exterior->xAt( i ) - mOriginX << exterior->yAt( i ) - mOriginY << ( mNoZ ? 0 : exterior->zAt( i ) );
if ( mAddNormals )
mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
}
else // Y axis is the up direction
{
mData << exterior->xAt( i ) - mOriginX << ( mNoZ ? 0 : exterior->zAt( i ) ) << - exterior->yAt( i ) + mOriginY;
if ( mAddNormals )
mData << -pNormal.x() << -pNormal.z() << pNormal.y();
}

if ( mAddTextureCoords )
{
std::pair<float, float> p( triangle->xAt( i ), triangle->yAt( i ) );
Expand Down Expand Up @@ -681,9 +718,19 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
if ( fz > zMaxExtruded )
zMaxExtruded = fz;

mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
if ( mOutputZUp )
{
mData << fx << fy << fz;
if ( mAddNormals )
mData << pNormal.x() << pNormal.y() << pNormal.z();
}
else
{
mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
}

if ( mAddTextureCoords )
{
const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
Expand All @@ -703,9 +750,20 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
const double fx = ( pt.x() / scale ) - mOriginX + pt0.x();
const double fy = ( pt.y() / scale ) - mOriginY + pt0.y();
const double fz = mNoZ ? 0 : ( pt.z() + extrusionHeight + pt0.z() );
mData << fx << fz << -fy;
if ( mAddNormals )
mData << -pNormal.x() << -pNormal.z() << pNormal.y();

if ( mOutputZUp )
{
mData << fx << fy << fz;
if ( mAddNormals )
mData << -pNormal.x() << -pNormal.y() << -pNormal.z();
}
else
{
mData << fx << fz << -fy;
if ( mAddNormals )
mData << -pNormal.x() << -pNormal.z() << pNormal.y();
}

if ( mAddTextureCoords )
{
const std::pair<float, float> pr = rotateCoords( p->x, p->y, 0.0f, 0.0f, mTextureRotation );
Expand All @@ -731,10 +789,10 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
// add walls if extrusion is enabled
if ( extrusionHeight != 0 && ( mTessellatedFacade & 1 ) )
{
_makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
_makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );

for ( int i = 0; i < polygon.numInteriorRings(); ++i )
_makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation );
_makeWalls( *qgsgeometry_cast< const QgsLineString * >( polygon.interiorRing( i ) ), true, extrusionHeight, mData, mAddNormals, mAddTextureCoords, mOriginX, mOriginY, mTextureRotation, mOutputZUp );

if ( zMaxBase + extrusionHeight > zMaxExtruded )
zMaxExtruded = zMaxBase + extrusionHeight;
Expand All @@ -760,11 +818,21 @@ std::unique_ptr<QgsMultiPolygon> QgsTessellator::asMultiPolygon() const
mp->reserve( nVals / 9 );
for ( auto i = decltype( nVals ) {0}; i + 8 < nVals; i += 9 )
{
// tessellator geometry is x, z, -y
const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
if ( mOutputZUp )
{
const QgsPoint p1( mData[i + 0], mData[i + 1], mData[i + 2] );
const QgsPoint p2( mData[i + 3], mData[i + 4], mData[i + 5] );
const QgsPoint p3( mData[i + 6], mData[i + 7], mData[i + 8] );
mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
}
else
{
// tessellator geometry is x, z, -y
const QgsPoint p1( mData[i + 0], -mData[i + 2], mData[i + 1] );
const QgsPoint p2( mData[i + 3], -mData[i + 5], mData[i + 4] );
const QgsPoint p3( mData[i + 6], -mData[i + 8], mData[i + 7] );
mp->addGeometry( new QgsTriangle( p1, p2, p3 ) );
}
}
return mp;
}
17 changes: 17 additions & 0 deletions src/core/qgstessellator.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,22 @@ class CORE_EXPORT QgsTessellator
QgsTessellator( const QgsRectangle &bounds, bool addNormals, bool invertNormals = false, bool addBackFaces = false, bool noZ = false,
bool addTextureCoords = false, int facade = 3, float textureRotation = 0.0f );

/**
* Sets whether the "up" direction should be the Z axis on output (true),
* otherwise the "up" direction will be the Y axis (false). The default
* value is false (to keep compatibility for existing tessellator use cases).
* \since QGIS 3.42
*/
void setOutputZUp( bool zUp ) { mOutputZUp = zUp; }

/**
* Returns whether the "up" direction should be the Z axis on output (true),
* otherwise the "up" direction will be the Y axis (false). The default
* value is false (to keep compatibility for existing tessellator use cases).
* \since QGIS 3.42
*/
bool isOutputZUp() const { return mOutputZUp; }

//! Tessellates a triangle and adds its vertex entries to the output data array
void addPolygon( const QgsPolygon &polygon, float extrusionHeight );

Expand Down Expand Up @@ -104,6 +120,7 @@ class CORE_EXPORT QgsTessellator
bool mInvertNormals = false;
bool mAddBackFaces = false;
bool mAddTextureCoords = false;
bool mOutputZUp = false;
QVector<float> mData;
int mStride;
bool mNoZ = false;
Expand Down
1 change: 1 addition & 0 deletions src/core/vector/qgsvectorlayerprofilegenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,7 @@ bool QgsVectorLayerProfileGenerator::generateProfileForPolygons()
{
const QgsRectangle bounds = clampedPolygon->boundingBox();
QgsTessellator t( bounds, false, false, false, false );
t.setOutputZUp( true );
t.addPolygon( *clampedPolygon, 0 );

tessellation = QgsGeometry( t.asMultiPolygon() );
Expand Down
Loading

0 comments on commit 4b3c54d

Please sign in to comment.