Skip to content

Commit

Permalink
More tessellation fixes and more unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
wonder-sk committed Oct 11, 2017
1 parent 8412a87 commit 006352f
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 38 deletions.
56 changes: 39 additions & 17 deletions src/3d/qgstessellator.cpp
Expand Up @@ -111,37 +111,60 @@ static void _makeWalls( const QgsCurve &ring, bool ccw, float extrusionHeight, Q
}
}

static QVector3D _calculateNormal( const QgsCurve *curve, bool &hasValidZ )
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 );

// assume that Z coordinates are not present
hasValidZ = false;
// 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 );

// 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;

hasValidZ = true;

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() ) );
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() );
}

if ( !hasValidZ )
return QVector3D( 0, 0, 1 );

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

return normal;
}

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

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

// Polygon is a triangle
Expand Down
95 changes: 74 additions & 21 deletions tests/src/3d/testqgstessellator.cpp
Expand Up @@ -38,20 +38,16 @@ struct TriangleCoords
//! Constructs from tessellator output. Note: tessellator outputs (X,-Z,Y) tuples for (X,Y,Z) input coords
TriangleCoords( const float *data, bool withNormal )
{
pts[0] = QVector3D( data[0], -data[2], data[1] );
pts[1] = QVector3D( data[3], -data[5], data[4] );
pts[2] = QVector3D( data[6], -data[8], data[7] );
if ( withNormal )
{
data += 9;
normals[0] = QVector3D( data[0], -data[2], data[1] );
normals[1] = QVector3D( data[3], -data[5], data[4] );
normals[2] = QVector3D( data[6], -data[8], data[7] );
}
else
{
normals[0] = normals[1] = normals[2] = QVector3D();
}
#define FLOAT3_TO_VECTOR(x) QVector3D( data[0], -data[2], data[1] )

pts[0] = FLOAT3_TO_VECTOR( data ); data += 3;
if ( withNormal ) { normals[0] = FLOAT3_TO_VECTOR( data ); data += 3; }

pts[1] = FLOAT3_TO_VECTOR( data ); data += 3;
if ( withNormal ) { normals[1] = FLOAT3_TO_VECTOR( data ); data += 3; }

pts[2] = FLOAT3_TO_VECTOR( data ); data += 3;
if ( withNormal ) { normals[2] = FLOAT3_TO_VECTOR( data ); data += 3; }
}

//! Compares two triangles
Expand All @@ -62,10 +58,47 @@ struct TriangleCoords
normals[0] == other.normals[0] && normals[1] == other.normals[1] && normals[2] == other.normals[2];
}

bool operator!=( const TriangleCoords &other ) const
{
return !operator==( other );
}

void dump() const
{
qDebug() << pts[0] << pts[1] << pts[2] << normals[0] << normals[1] << normals[2];
}

QVector3D pts[3];
QVector3D normals[3];
};


bool checkTriangleOutput( const QVector<float> &data, bool withNormals, const QList<TriangleCoords> &expected )
{
int valuesPerTriangle = withNormals ? 18 : 9;
if ( data.count() != expected.count() * valuesPerTriangle )
return false;

// TODO: allow arbitrary order of triangles in output
const float *dataRaw = data.constData();
for ( int i = 0; i < expected.count(); ++i )
{
const TriangleCoords &exp = expected.at( i );
TriangleCoords out( dataRaw, withNormals );
if ( exp != out )
{
qDebug() << "expected:";
exp.dump();
qDebug() << "got:";
out.dump();
return false;
}
dataRaw += withNormals ? 18 : 9;
}
return true;
}


/**
* \ingroup UnitTests
* This is a unit test for the node tool
Expand Down Expand Up @@ -103,17 +136,37 @@ void TestQgsTessellator::testBasic()
QgsPolygonV2 polygon;
polygon.fromWkt( "POLYGON((1 1, 2 1, 3 2, 1 2, 1 1))" );

QgsPolygonV2 polygonZ;
polygonZ.fromWkt( "POLYGONZ((1 1 0, 2 1 0, 3 2 0, 1 2 0, 1 1 0))" );

QList<TriangleCoords> tc;
tc << TriangleCoords( QVector3D( 1, 2, 0 ), QVector3D( 2, 1, 0 ), QVector3D( 3, 2, 0 ) );
tc << TriangleCoords( QVector3D( 1, 2, 0 ), QVector3D( 1, 1, 0 ), QVector3D( 2, 1, 0 ) );

QVector3D up( 0, 0, 1 ); // surface normal pointing straight up
QList<TriangleCoords> tcNormals;
tcNormals << TriangleCoords( QVector3D( 1, 2, 0 ), QVector3D( 2, 1, 0 ), QVector3D( 3, 2, 0 ), up, up, up );
tcNormals << TriangleCoords( QVector3D( 1, 2, 0 ), QVector3D( 1, 1, 0 ), QVector3D( 2, 1, 0 ), up, up, up );

// without normals

QgsTessellator t( 0, 0, false );
t.addPolygon( polygon, 0 );
QVERIFY( checkTriangleOutput( t.data(), false, tc ) );

TriangleCoords tcA( QVector3D( 1, 2, 0 ), QVector3D( 2, 1, 0 ), QVector3D( 3, 2, 0 ) );
TriangleCoords tcB( QVector3D( 1, 2, 0 ), QVector3D( 1, 1, 0 ), QVector3D( 2, 1, 0 ) );
QgsTessellator tZ( 0, 0, false );
tZ.addPolygon( polygonZ, 0 );
QVERIFY( checkTriangleOutput( tZ.data(), false, tc ) );

QVector<float> polygonData = t.data();
QCOMPARE( polygonData.count(), 2 * 3 * 3 ); // two triangles (3 points with x/y/z coords)
// TODO: allow arbitrary order of triangles in output
QVERIFY( tcA == TriangleCoords( polygonData.constData(), false ) );
QVERIFY( tcB == TriangleCoords( polygonData.constData() + 9, false ) );
// with normals

QgsTessellator tN( 0, 0, true );
tN.addPolygon( polygon, 0 );
QVERIFY( checkTriangleOutput( tN.data(), true, tcNormals ) );

QgsTessellator tNZ( 0, 0, true );
tNZ.addPolygon( polygonZ, 0 );
QVERIFY( checkTriangleOutput( tNZ.data(), true, tcNormals ) );
}


Expand Down

0 comments on commit 006352f

Please sign in to comment.