mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-26 00:02:08 -05:00
More tessellation fixes and more unit tests
This commit is contained in:
parent
8412a87c00
commit
006352f128
@ -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
|
||||
|
@ -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 ) );
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user