Skip to content

Commit

Permalink
Merge pull request #5342 from wonder-sk/tessellator-fixes
Browse files Browse the repository at this point in the history
Fixes to make tessellation of polygons without Z work again
  • Loading branch information
wonder-sk committed Oct 11, 2017
2 parents 33d2373 + 006352f commit 6c1d570
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/3d/qgscameracontroller.cpp
Expand Up @@ -236,7 +236,7 @@ void QgsCameraController::frameTriggered( float dt )
mCameraData.y -= p2.y() - p1.y();
}

if ( qIsNaN( mCameraData.x ) || qIsNaN( mCameraData.y ) )
if ( std::isnan( mCameraData.x ) || std::isnan( mCameraData.y ) )
{
// something went horribly wrong but we need to at least try to fix it somehow
qDebug() << "camera position got NaN!";
Expand Down
67 changes: 51 additions & 16 deletions src/3d/qgstessellator.cpp
Expand Up @@ -111,26 +111,60 @@ static void _makeWalls( const QgsCurve &ring, bool ccw, float extrusionHeight, Q
}
}

static QVector3D _calculateNormal( const QgsCurve *curve )
static QVector3D _calculateNormal( const QgsCurve *curve, double originX, double originY )
{
// Calculate the polygon's normal vector, based on Newell's method
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
QgsVertexId::VertexType vt;
QgsPoint pt1, pt2;
QVector3D normal( 0, 0, 0 );

// if it is just plain 2D curve there is no need to calculate anything
// because it will be a flat horizontally oriented patch
if ( !QgsWkbTypes::hasZ( curve->wkbType() ) )
return QVector3D( 0, 0, 1 );

// often we have 3D coordinates, but Z is the same for all vertices
// so in order to save calculation and avoid possible issues with order of vertices
// (the calculation below may decide that a polygon faces downwards)
bool sameZ = true;
curve->pointAt( 0, pt1, vt );
for ( int i = 1; i < curve->numPoints(); i++ )
{
curve->pointAt( i, pt2, vt );
if ( pt1.z() != pt2.z() )
{
sameZ = false;
break;
}
}
if ( sameZ )
return QVector3D( 0, 0, 1 );

// Calculate the polygon's normal vector, based on Newell's method
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
//
// Order of vertices is important here as it determines the front/back face of the polygon

double nx = 0, ny = 0, nz = 0;
for ( int i = 0; i < curve->numPoints() - 1; i++ )
{
curve->pointAt( i, pt1, vt );
curve->pointAt( i + 1, pt2, vt );

normal.setX( normal.x() + ( pt1.y() - pt2.y() ) * ( pt1.z() + pt2.z() ) );
normal.setY( normal.y() + ( pt1.z() - pt2.z() ) * ( pt1.x() + pt2.x() ) );
normal.setZ( normal.z() + ( pt1.x() - pt2.x() ) * ( pt1.y() + pt2.y() ) );
// shift points by the tessellator's origin - this does not affect normal calculation and it may save us from losing some precision
pt1.setX( pt1.x() - originX );
pt1.setY( pt1.y() - originY );
pt2.setX( pt2.x() - originX );
pt2.setY( pt2.y() - originY );

if ( std::isnan( pt1.z() ) || std::isnan( pt2.z() ) )
continue;

nx += ( pt1.y() - pt2.y() ) * ( pt1.z() + pt2.z() );
ny += ( pt1.z() - pt2.z() ) * ( pt1.x() + pt2.x() );
nz += ( pt1.x() - pt2.x() ) * ( pt1.y() + pt2.y() );
}

QVector3D normal( nx, ny, nz );
normal.normalize();

return normal;
}

Expand Down Expand Up @@ -183,7 +217,7 @@ void QgsTessellator::addPolygon( const QgsPolygonV2 &polygon, float extrusionHei
QgsVertexId::VertexType vt;
QgsPoint pt;

const QVector3D pNormal = _calculateNormal( exterior );
const QVector3D pNormal = _calculateNormal( exterior, mOriginX, mOriginY );
const int pCount = exterior->numPoints();

// Polygon is a triangle
Expand All @@ -207,7 +241,8 @@ void QgsTessellator::addPolygon( const QgsPolygonV2 &polygon, float extrusionHei
}

const QgsPoint ptFirst( exterior->startPoint() );
QVector3D pOrigin( ptFirst.x(), ptFirst.y(), ptFirst.z() ), pXVector;
QVector3D pOrigin( ptFirst.x(), ptFirst.y(), std::isnan( ptFirst.z() ) ? 0 : ptFirst.z() );
QVector3D pXVector;
// Here we define the two perpendicular vectors that define the local
// 2D space on the plane. They will act as axis for which we will
// calculate the projection coordinates of a 3D point to the plane.
Expand All @@ -229,7 +264,7 @@ void QgsTessellator::addPolygon( const QgsPolygonV2 &polygon, float extrusionHei
for ( int i = 0; i < pCount - 1; ++i )
{
exterior->pointAt( i, pt, vt );
QVector3D tempPt( pt.x(), pt.y(), ( qIsNaN( pt.z() ) ? 0 : pt.z() ) );
QVector3D tempPt( pt.x(), pt.y(), ( std::isnan( pt.z() ) ? 0 : pt.z() ) );
const float x = QVector3D::dotProduct( tempPt - pOrigin, pXVector );
const float y = QVector3D::dotProduct( tempPt - pOrigin, pYVector );

Expand All @@ -243,7 +278,7 @@ void QgsTessellator::addPolygon( const QgsPolygonV2 &polygon, float extrusionHei
p2t::Point *pt2 = new p2t::Point( x, y );
polyline.push_back( pt2 );

z[pt2] = qIsNaN( pt.z() ) ? 0 : pt.z();
z[pt2] = std::isnan( pt.z() ) ? 0 : pt.z();
}
polylinesToDelete << polyline;

Expand All @@ -258,7 +293,7 @@ void QgsTessellator::addPolygon( const QgsPolygonV2 &polygon, float extrusionHei
QVector3D nPoint = pOrigin + pXVector * p->x + pYVector * p->y;
const double fx = nPoint.x() - mOriginX;
const double fy = nPoint.y() - mOriginY;
const double fz = extrusionHeight + ( qIsNaN( zPt ) ? 0 : zPt );
const double fz = extrusionHeight + ( std::isnan( zPt ) ? 0 : zPt );
mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
Expand All @@ -277,7 +312,7 @@ void QgsTessellator::addPolygon( const QgsPolygonV2 &polygon, float extrusionHei
for ( int j = 0; j < hole->numPoints() - 1; ++j )
{
hole->pointAt( j, pt, vt );
QVector3D tempPt( pt.x(), pt.y(), ( qIsNaN( pt.z() ) ? 0 : pt.z() ) );
QVector3D tempPt( pt.x(), pt.y(), ( std::isnan( pt.z() ) ? 0 : pt.z() ) );

const float x = QVector3D::dotProduct( tempPt - pOrigin, pXVector );
const float y = QVector3D::dotProduct( tempPt - pOrigin, pYVector );
Expand All @@ -292,7 +327,7 @@ void QgsTessellator::addPolygon( const QgsPolygonV2 &polygon, float extrusionHei
p2t::Point *pt2 = new p2t::Point( x, y );
holePolyline.push_back( pt2 );

z[pt2] = qIsNaN( pt.z() ) ? 0 : pt.z();
z[pt2] = std::isnan( pt.z() ) ? 0 : pt.z();
}
cdt->AddHole( holePolyline );
polylinesToDelete << holePolyline;
Expand All @@ -313,7 +348,7 @@ void QgsTessellator::addPolygon( const QgsPolygonV2 &polygon, float extrusionHei
QVector3D nPoint = pOrigin + pXVector * p->x + pYVector * p->y;
float fx = nPoint.x() - mOriginX;
float fy = nPoint.y() - mOriginY;
float fz = extrusionHeight + ( qIsNaN( zPt ) ? 0 : zPt );
float fz = extrusionHeight + ( std::isnan( zPt ) ? 0 : zPt );
mData << fx << fz << -fy;
if ( mAddNormals )
mData << pNormal.x() << pNormal.z() << - pNormal.y();
Expand Down
4 changes: 3 additions & 1 deletion src/3d/qgstessellator.h
Expand Up @@ -16,6 +16,8 @@
#ifndef QGSTESSELLATOR_H
#define QGSTESSELLATOR_H

#include "qgis_3d.h"

class QgsPolygonV2;

#include <QVector>
Expand All @@ -32,7 +34,7 @@ class QgsPolygonV2;
*
* \since QGIS 3.0
*/
class QgsTessellator
class _3D_EXPORT QgsTessellator
{
public:
//! Creates tessellator with a specified origin point of the world (in map coordinates)
Expand Down
71 changes: 71 additions & 0 deletions tests/src/3d/CMakeLists.txt
@@ -0,0 +1,71 @@
# Standard includes and utils to compile into all tests.
SET (util_SRCS)


#####################################################
# Don't forget to include output directory, otherwise
# the UI file won't be wrapped!
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/tests/core #for render checker class
${CMAKE_SOURCE_DIR}/src/3d
${CMAKE_SOURCE_DIR}/src/core
${CMAKE_SOURCE_DIR}/src/core/expression
${CMAKE_SOURCE_DIR}/src/core/auth
${CMAKE_SOURCE_DIR}/src/core/composer
${CMAKE_SOURCE_DIR}/src/core/geometry
${CMAKE_SOURCE_DIR}/src/core/layout
${CMAKE_SOURCE_DIR}/src/core/metadata
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/core/symbology
${CMAKE_SOURCE_DIR}/src/core/fieldformatter
${CMAKE_SOURCE_DIR}/src/test
${CMAKE_SOURCE_DIR}/src/native


${CMAKE_BINARY_DIR}/src/core
${CMAKE_BINARY_DIR}/src/3d
${CMAKE_BINARY_DIR}/src/ui
${CMAKE_BINARY_DIR}/src/native
${CMAKE_CURRENT_BINARY_DIR}
)
INCLUDE_DIRECTORIES(SYSTEM
${QT_INCLUDE_DIR}
${GDAL_INCLUDE_DIR}
${PROJ_INCLUDE_DIR}
${GEOS_INCLUDE_DIR}
)

#note for tests we should not include the moc of our
#qtests in the executable file list as the moc is
#directly included in the sources
#and should not be compiled twice. Trying to include
#them in will cause an error at build time
#############################################################
# Tests:

MACRO (ADD_QGIS_TEST testname testsrc)
SET(qgis_${testname}_SRCS ${testsrc} ${util_SRCS})
SET(qgis_${testname}_MOC_CPPS ${testsrc})
ADD_EXECUTABLE(qgis_${testname} ${qgis_${testname}_SRCS})
SET_TARGET_PROPERTIES(qgis_${testname} PROPERTIES AUTOMOC TRUE)
SET_TARGET_PROPERTIES(qgis_${testname} PROPERTIES AUTORCC TRUE)
TARGET_LINK_LIBRARIES(qgis_${testname}
${QT_QTXML_LIBRARY}
${QT_QTCORE_LIBRARY}
${QT_QTSVG_LIBRARY}
${QT_QTTEST_LIBRARY}
${QT_QTNETWORK_LIBRARY}
${PROJ_LIBRARY}
${GEOS_LIBRARY}
${GDAL_LIBRARY}
qgis_core
qgis_3d)
ADD_TEST(qgis_${testname} ${CMAKE_CURRENT_BINARY_DIR}/../../../output/bin/qgis_${testname} -maxwarnings 10000)
TARGET_LINK_LIBRARIES(qgis_${testname} qgis_native)
ADD_TEST(qgis_${testname} ${CMAKE_CURRENT_BINARY_DIR}/../../../output/bin/qgis_${testname})
#SET_TARGET_PROPERTIES(qgis_${testname} PROPERTIES
# INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/${QGIS_LIB_DIR}
# INSTALL_RPATH_USE_LINK_PATH true )
ENDMACRO (ADD_QGIS_TEST)

ADD_QGIS_TEST(tessellatortest testqgstessellator.cpp)

0 comments on commit 6c1d570

Please sign in to comment.