Skip to content

Commit

Permalink
Fix tessellation of polygons that are not horizontal
Browse files Browse the repository at this point in the history
Discovered by Nyall while working on PR #5708

Tessellation would shift coordinates because when points got reprojected
to the new base, the Z coordinates were considered zero (which worked only
when all points were on the same plane).
  • Loading branch information
wonder-sk committed Nov 23, 2017
1 parent d1cf7e6 commit a2ff363
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 18 deletions.
42 changes: 28 additions & 14 deletions src/3d/qgstessellator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "poly2tri/poly2tri.h"

#include <QtDebug>
#include <QMatrix4x4>
#include <QVector3D>
#include <algorithm>

Expand Down Expand Up @@ -193,7 +194,7 @@ static void _normalVectorToXYVectors( const QVector3D &pNormal, QVector3D &pXVec
}


static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, const QVector3D &pXVector, const QVector3D &pYVector, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash )
static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, const QMatrix4x4 &toNewBase, std::vector<p2t::Point *> &polyline, QHash<p2t::Point *, float> &zHash )
{
QgsVertexId::VertexType vt;
QgsPoint pt;
Expand All @@ -206,10 +207,11 @@ static void _ringToPoly2tri( const QgsCurve *ring, const QgsPoint &ptFirst, cons
for ( int i = 0; i < pCount - 1; ++i )
{
ring->pointAt( i, pt, vt );
const float z = std::isnan( pt.z() ) ? 0 : pt.z();
QVector3D tempPt( pt.x() - x0, pt.y() - y0, z - z0 );
const float x = QVector3D::dotProduct( tempPt, pXVector );
const float y = QVector3D::dotProduct( tempPt, pYVector );
QVector4D tempPt( pt.x() - x0, pt.y() - y0, std::isnan( pt.z() ) ? 0 : pt.z() - z0, 0 );
QVector4D newBasePt = toNewBase * tempPt;
const float x = newBasePt.x();
const float y = newBasePt.y();
const float z = newBasePt.z();

const bool found = std::find_if( polyline.begin(), polyline.end(), [x, y]( p2t::Point *&p ) { return *p == p2t::Point( x, y ); } ) != polyline.end();

Expand Down Expand Up @@ -301,23 +303,35 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
QVector3D pXVector, pYVector;
_normalVectorToXYVectors( pNormal, pXVector, pYVector );

// so now we have three orthogonal unit vectors defining new base
// let's build transform matrix. We actually need just a 3x3 matrix,
// but Qt does not have good support for it, so using 4x4 matrix instead.
QMatrix4x4 toNewBase(
pXVector.x(), pXVector.y(), pXVector.z(), 0,
pYVector.x(), pYVector.y(), pYVector.z(), 0,
pNormal.x(), pNormal.y(), pNormal.z(), 0,
0, 0, 0, 0 );

// our 3x3 matrix is orthogonal, so for inverse we only need to transpose it
QMatrix4x4 toOldBase = toNewBase.transposed();

const QgsPoint ptFirst( exterior->startPoint() );
_ringToPoly2tri( exterior, ptFirst, pXVector, pYVector, polyline, z );
_ringToPoly2tri( exterior, ptFirst, toNewBase, polyline, z );
polylinesToDelete << polyline;

// TODO: robustness (no nearly duplicate points, invalid geometries ...)

double x0 = ptFirst.x(), y0 = ptFirst.y();
double x0 = ptFirst.x(), y0 = ptFirst.y(), z0 = ( std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() );
if ( polyline.size() == 3 && polygon.numInteriorRings() == 0 )
{
for ( std::vector<p2t::Point *>::iterator it = polyline.begin(); it != polyline.end(); it++ )
{
p2t::Point *p = *it;
const double zPt = z[p];
QVector3D nPoint = pXVector * p->x + pYVector * p->y;
QVector4D ptInNewBase( p->x, p->y, z[p], 0 );
QVector4D nPoint = toOldBase * ptInNewBase;
const double fx = nPoint.x() - mOriginX + x0;
const double fy = nPoint.y() - mOriginY + y0;
const double fz = extrusionHeight + ( std::isnan( zPt ) ? 0 : zPt );
const double fz = nPoint.z() + extrusionHeight + z0;
mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
Expand All @@ -333,7 +347,7 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
std::vector<p2t::Point *> holePolyline;
const QgsCurve *hole = polygon.interiorRing( i );

_ringToPoly2tri( hole, ptFirst, pXVector, pYVector, holePolyline, z );
_ringToPoly2tri( hole, ptFirst, toNewBase, holePolyline, z );

cdt->AddHole( holePolyline );
polylinesToDelete << holePolyline;
Expand All @@ -351,11 +365,11 @@ void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeigh
for ( int j = 0; j < 3; ++j )
{
p2t::Point *p = t->GetPoint( j );
const double zPt = z[p];
QVector3D nPoint = pXVector * p->x + pYVector * p->y;
QVector4D ptInNewBase( p->x, p->y, z[p], 0 );
QVector4D nPoint = toOldBase * ptInNewBase;
const double fx = nPoint.x() - mOriginX + x0;
const double fy = nPoint.y() - mOriginY + y0;
const double fz = extrusionHeight + ( std::isnan( zPt ) ? 0 : zPt );
const double fz = nPoint.z() + extrusionHeight + z0;
mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
Expand Down
6 changes: 2 additions & 4 deletions tests/src/3d/testqgstessellator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,8 @@ void TestQgsTessellator::testWalls()

QList<TriangleCoords> tc;

// NOTE - these coordinates are wrong, and this test exposes a different bug in the tesselator
// 2.4 should be 2:
tc << TriangleCoords( QVector3D( 1, 2.4, 14 ), QVector3D( 2, 1.4, 12 ), QVector3D( 3, 2, 13 ) );
tc << TriangleCoords( QVector3D( 1, 2.4, 14 ), QVector3D( 1, 1, 11 ), QVector3D( 2, 1.4, 12 ) );
tc << TriangleCoords( QVector3D( 1, 2, 14 ), QVector3D( 2, 1, 12 ), QVector3D( 3, 2, 13 ) );
tc << TriangleCoords( QVector3D( 1, 2, 14 ), QVector3D( 1, 1, 11 ), QVector3D( 2, 1, 12 ) );

tc << TriangleCoords( QVector3D( 1, 1, 11 ), QVector3D( 1, 2, 14 ), QVector3D( 1, 1, 1 ) );
tc << TriangleCoords( QVector3D( 1, 1, 1 ), QVector3D( 1, 2, 14 ), QVector3D( 1, 2, 4 ) );
Expand Down

0 comments on commit a2ff363

Please sign in to comment.