More tessellation fixes and more unit tests

This commit is contained in:
Martin Dobias 2017-10-11 12:33:47 +02:00
parent 8412a87c00
commit 006352f128
2 changed files with 113 additions and 38 deletions

View File

@ -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;
}
@ -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

View File

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