Example #1
0
void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
{
  if ( _minimum_distance_between_coordinates( polygon ) < 0.001 )
  {
    // when the distances between coordinates of input points are very small,
    // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
    // Assuming that the coordinates should be in a projected CRS, we should be able
    // to simplify geometries that may cause problems and avoid possible crashes
    QgsGeometry polygonSimplified = QgsGeometry( polygon.clone() ).simplify( 0.001 );
    const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
    if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
    {
      // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
      // geometry in unprojected lat/lon coordinates
      QgsMessageLog::logMessage( "geometry's coordinates are too close to each other and simplification failed - skipping", "3D" );
    }
    else
    {
      addPolygon( *polygonSimplifiedData, extrusionHeight );
    }
    return;
  }

  if ( !_check_intersecting_rings( polygon ) )
  {
    // skip the polygon - it would cause a crash inside poly2tri library
    QgsMessageLog::logMessage( "polygon rings intersect each other - skipping", "3D" );
    return;
  }

  const QgsCurve *exterior = polygon.exteriorRing();

  QList< std::vector<p2t::Point *> > polylinesToDelete;
  QHash<p2t::Point *, float> z;

  std::vector<p2t::Point *> polyline;

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

  // Polygon is a triangle
  if ( pCount == 4 )
  {
    QgsPoint pt;
    QgsVertexId::VertexType vt;
    for ( int i = 0; i < 3; i++ )
    {
      exterior->pointAt( i, pt, vt );
      mData << pt.x() - mOriginX << pt.z() << - pt.y() + mOriginY;
      if ( mAddNormals )
        mData << pNormal.x() << pNormal.z() << - pNormal.y();
    }
  }
  else
  {
    if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
      return;  // this should not happen - pNormal should be normalized to unit length

    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, toNewBase, polyline, z );
    polylinesToDelete << polyline;

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

    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;
        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 = nPoint.z() + extrusionHeight + z0;
        mData << fx << fz << -fy;
        if ( mAddNormals )
          mData << pNormal.x() << pNormal.z() << - pNormal.y();
      }
    }
    else if ( polyline.size() >= 3 )
    {
      p2t::CDT *cdt = new p2t::CDT( polyline );

      // polygon holes
      for ( int i = 0; i < polygon.numInteriorRings(); ++i )
      {
        std::vector<p2t::Point *> holePolyline;
        const QgsCurve *hole = polygon.interiorRing( i );

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

        cdt->AddHole( holePolyline );
        polylinesToDelete << holePolyline;
      }

      try
      {
        cdt->Triangulate();

        std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();

        for ( size_t i = 0; i < triangles.size(); ++i )
        {
          p2t::Triangle *t = triangles[i];
          for ( int j = 0; j < 3; ++j )
          {
            p2t::Point *p = t->GetPoint( j );
            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 = nPoint.z() + extrusionHeight + z0;
            mData << fx << fz << -fy;
            if ( mAddNormals )
              mData << pNormal.x() << pNormal.z() << - pNormal.y();
          }
        }
      }
      catch ( ... )
      {
        QgsMessageLog::logMessage( "Triangulation failed. Skipping polygon...", "3D" );
      }

      delete cdt;
    }

    for ( int i = 0; i < polylinesToDelete.count(); ++i )
      qDeleteAll( polylinesToDelete[i] );
  }

  // add walls if extrusion is enabled
  if ( extrusionHeight != 0 )
  {
    _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );

    for ( int i = 0; i < polygon.numInteriorRings(); ++i )
      _makeWalls( *polygon.interiorRing( i ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
  }
}
Example #2
0
void QgsTessellator::addPolygon( const QgsPolygon &polygon, float extrusionHeight )
{
  const QgsCurve *exterior = polygon.exteriorRing();

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

  if ( pCount == 4 && polygon.numInteriorRings() == 0 )
  {
    // polygon is a triangle - write vertices to the output data array without triangulation
    QgsPoint pt;
    QgsVertexId::VertexType vt;
    for ( int i = 0; i < 3; i++ )
    {
      exterior->pointAt( i, pt, vt );
      mData << pt.x() - mOriginX << pt.z() << - pt.y() + mOriginY;
      if ( mAddNormals )
        mData << pNormal.x() << pNormal.z() << - pNormal.y();
    }

    if ( mAddBackFaces )
    {
      // the same triangle with reversed order of coordinates and inverted normal
      for ( int i = 2; i >= 0; i-- )
      {
        exterior->pointAt( i, pt, vt );
        mData << pt.x() - mOriginX << pt.z() << - pt.y() + mOriginY;
        if ( mAddNormals )
          mData << -pNormal.x() << -pNormal.z() << pNormal.y();
      }
    }
  }
  else
  {
    if ( !qgsDoubleNear( pNormal.length(), 1, 0.001 ) )
      return;  // this should not happen - pNormal should be normalized to unit length

    std::unique_ptr<QMatrix4x4> toNewBase, toOldBase;
    if ( pNormal != QVector3D( 0, 0, 1 ) )
    {
      // this is not a horizontal plane - need to reproject the polygon to a new base so that
      // we can do the triangulation in a plane

      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.
      toNewBase.reset( new QMatrix4x4(
                         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
      toOldBase.reset( new QMatrix4x4( toNewBase->transposed() ) );
    }

    const QgsPoint ptStart( exterior->startPoint() );
    const QgsPoint pt0( QgsWkbTypes::PointZ, ptStart.x(), ptStart.y(), std::isnan( ptStart.z() ) ? 0 : ptStart.z() );

    // subtract ptFirst from geometry for better numerical stability in triangulation
    // and apply new 3D vector base if the polygon is not horizontal
    std::unique_ptr<QgsPolygon> polygonNew( _transform_polygon_to_new_base( polygon, pt0, toNewBase.get() ) );

    if ( _minimum_distance_between_coordinates( *polygonNew ) < 0.001 )
    {
      // when the distances between coordinates of input points are very small,
      // the triangulation likes to crash on numerical errors - when the distances are ~ 1e-5
      // Assuming that the coordinates should be in a projected CRS, we should be able
      // to simplify geometries that may cause problems and avoid possible crashes
      QgsGeometry polygonSimplified = QgsGeometry( polygonNew->clone() ).simplify( 0.001 );
      const QgsPolygon *polygonSimplifiedData = qgsgeometry_cast<const QgsPolygon *>( polygonSimplified.constGet() );
      if ( _minimum_distance_between_coordinates( *polygonSimplifiedData ) < 0.001 )
      {
        // Failed to fix that. It could be a really tiny geometry... or maybe they gave us
        // geometry in unprojected lat/lon coordinates
        QgsMessageLog::logMessage( QObject::tr( "geometry's coordinates are too close to each other and simplification failed - skipping" ), QObject::tr( "3D" ) );
        return;
      }
      else
      {
        polygonNew.reset( polygonSimplifiedData->clone() );
      }
    }

    if ( !_check_intersecting_rings( *polygonNew.get() ) )
    {
      // skip the polygon - it would cause a crash inside poly2tri library
      QgsMessageLog::logMessage( QObject::tr( "polygon rings self-intersect or intersect each other - skipping" ), QObject::tr( "3D" ) );
      return;
    }

    QList< std::vector<p2t::Point *> > polylinesToDelete;
    QHash<p2t::Point *, float> z;

    // polygon exterior
    std::vector<p2t::Point *> polyline;
    _ringToPoly2tri( polygonNew->exteriorRing(), polyline, z );
    polylinesToDelete << polyline;

    std::unique_ptr<p2t::CDT> cdt( new p2t::CDT( polyline ) );

    // polygon holes
    for ( int i = 0; i < polygonNew->numInteriorRings(); ++i )
    {
      std::vector<p2t::Point *> holePolyline;
      const QgsCurve *hole = polygonNew->interiorRing( i );

      _ringToPoly2tri( hole, holePolyline, z );

      cdt->AddHole( holePolyline );
      polylinesToDelete << holePolyline;
    }

    // run triangulation and write vertices to the output data array
    try
    {
      cdt->Triangulate();

      std::vector<p2t::Triangle *> triangles = cdt->GetTriangles();

      for ( size_t i = 0; i < triangles.size(); ++i )
      {
        p2t::Triangle *t = triangles[i];
        for ( int j = 0; j < 3; ++j )
        {
          p2t::Point *p = t->GetPoint( j );
          QVector4D pt( p->x, p->y, z[p], 0 );
          if ( toOldBase )
            pt = *toOldBase * pt;
          const double fx = pt.x() - mOriginX + pt0.x();
          const double fy = pt.y() - mOriginY + pt0.y();
          const double fz = pt.z() + extrusionHeight + pt0.z();
          mData << fx << fz << -fy;
          if ( mAddNormals )
            mData << pNormal.x() << pNormal.z() << - pNormal.y();
        }

        if ( mAddBackFaces )
        {
          // the same triangle with reversed order of coordinates and inverted normal
          for ( int j = 2; j >= 0; --j )
          {
            p2t::Point *p = t->GetPoint( j );
            QVector4D pt( p->x, p->y, z[p], 0 );
            if ( toOldBase )
              pt = *toOldBase * pt;
            const double fx = pt.x() - mOriginX + pt0.x();
            const double fy = pt.y() - mOriginY + pt0.y();
            const double fz = pt.z() + extrusionHeight + pt0.z();
            mData << fx << fz << -fy;
            if ( mAddNormals )
              mData << -pNormal.x() << -pNormal.z() << pNormal.y();
          }
        }
      }
    }
    catch ( ... )
    {
      QgsMessageLog::logMessage( QObject::tr( "Triangulation failed. Skipping polygon…" ), QObject::tr( "3D" ) );
    }

    for ( int i = 0; i < polylinesToDelete.count(); ++i )
      qDeleteAll( polylinesToDelete[i] );
  }

  // add walls if extrusion is enabled
  if ( extrusionHeight != 0 )
  {
    _makeWalls( *exterior, false, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );

    for ( int i = 0; i < polygon.numInteriorRings(); ++i )
      _makeWalls( *polygon.interiorRing( i ), true, extrusionHeight, mData, mAddNormals, mOriginX, mOriginY );
  }
}