예제 #1
0
void tst_QGeometryData::appendNormal()
{
    QVector3D a(1.1, 1.2, 1.3);
    QVector3D b(2.1, 2.2, 2.3);
    QVector3D c(3.1, 3.2, 3.3);
    QVector3D d(4.1, 4.2, 4.3);
    {
        QGeometryData data;
        data.appendNormal(a);
        QCOMPARE(data.count(), 1);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.normals().count(), 1);
        QCOMPARE(data.normals().at(0), a);
    }
    {
        QGeometryData data;
        data.appendNormal(a, b);
        QCOMPARE(data.count(), 2);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.normals().count(), 2);
        QCOMPARE(data.normals().at(0), a);
        QCOMPARE(data.normals().at(1), b);
    }
    {
        QGeometryData data;
        data.appendNormal(a, b, c);
        QCOMPARE(data.count(), 3);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.normals().count(), 3);
        QCOMPARE(data.normals().at(0), a);
        QCOMPARE(data.normals().at(1), b);
        QCOMPARE(data.normals().at(2), c);
    }
    {
        QGeometryData data;
        data.appendNormal(a, b, c, d);
        QCOMPARE(data.count(), 4);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.normals().count(), 4);
        QCOMPARE(data.normals().at(0), a);
        QCOMPARE(data.normals().at(1), b);
        QCOMPARE(data.normals().at(2), c);
        QCOMPARE(data.normals().at(3), d);
    }
    {
        QGeometryData data;
        data.appendNormal(a, b, c, d);
        data.appendNormal(a, b, c, d);
        data.appendNormal(a);
        QCOMPARE(data.count(), 9);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.normals().count(), 9);
        QCOMPARE(data.normals().at(0), a);
        QCOMPARE(data.normals().at(1), b);
        QCOMPARE(data.normals().at(5), b);
        QCOMPARE(data.normals().at(8), a);
    }
}
예제 #2
0
void QGLBezierPatchesPrivate::subdivide(QGLBuilder *list) const
{
    QGeometryData prim;
    int count = positions.size();
    for (int posn = 0; (posn + 15) < count; posn += 16) {
        // Construct a QGLBezierPatch object from the next high-level patch.
        QGLBezierPatch patch;
        int vertex;
        for (int vertex = 0; vertex < 16; ++vertex)
            patch.points[vertex] = positions[posn + vertex];
        QVector2D tex1, tex2;
        if (!textureCoords.isEmpty()) {
            tex1 = textureCoords[(posn / 16) * 2];
            tex2 = textureCoords[(posn / 16) * 2 + 1];
        } else {
            tex1 = QVector2D(0.0f, 0.0f);
            tex2 = QVector2D(1.0f, 1.0f);
        }
        qreal xtex = tex1.x();
        qreal ytex = tex1.y();
        qreal wtex = tex2.x() - xtex;
        qreal htex = tex2.y() - ytex;
        for (int corner = 0; corner < 4; ++corner) {
            vertex = posn + cornerOffsets[corner];
            QVector3D n = patch.normal(cornerS[corner], cornerT[corner]);
            patch.indices[corner] = prim.count();
            prim.appendVertex(patch.points[cornerOffsets[corner]]);
            prim.appendNormal(n);
            prim.appendTexCoord
                (QVector2D(xtex + wtex * cornerS[corner],
                           ytex + htex * cornerT[corner]));
        }

        // Subdivide the patch and generate the final triangles.
        patch.recursiveSubDivide(&prim, subdivisionDepth,
                                 xtex, ytex, wtex, htex);
    }
    list->addTriangles(prim);
}
예제 #3
0
void tst_QGeometryData::appendVertexNormal()
{
    QVector3D a(1.1, 1.2, 1.3);
    QVector3D b(2.1, 2.2, 2.3);
    QVector3D c(3.1, 3.2, 3.3);
    QVector3D d(4.1, 4.2, 4.3);
    QVector3D an(5.1, 5.2, 5.3);
    QVector3D bn(6.1, 6.2, 6.3);
    QVector3D cn(7.1, 7.2, 7.3);
    QVector3D dn(8.1, 8.2, 8.3);
    {
        QGeometryData data;
        data.appendVertex(a);
        data.appendNormal(an);
        QCOMPARE(data.count(), 1);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Position) | QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.vertices().count(), 1);
        QCOMPARE(data.vertices().at(0), a);
    }
    {
        QGeometryData data;
        data.appendVertex(a, b);
        data.appendNormal(an, bn);
        QCOMPARE(data.count(), 2);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Position) | QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.vertices().count(), 2);
        QCOMPARE(data.vertices().at(0), a);
        QCOMPARE(data.vertex(1), b);
        QCOMPARE(data.normals().count(), 2);
        QCOMPARE(data.normal(0), an);
        QCOMPARE(data.normals().at(1), bn);
    }
    {
        QGeometryData data;
        data.appendVertex(a, b, c);
        data.appendNormal(an, bn, cn);
        QCOMPARE(data.count(), 3);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Position) | QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.vertices().count(), 3);
        QCOMPARE(data.vertices().at(0), a);
        QCOMPARE(data.vertices().at(1), b);
        QCOMPARE(data.vertices().at(2), c);
        QCOMPARE(data.normals().count(), 3);
        QCOMPARE(data.normal(0), an);
        QCOMPARE(data.normals().at(1), bn);
        QCOMPARE(data.normal(2), cn);
    }
    {
        QGeometryData data;
        data.appendVertex(a, b, c, d);
        data.appendNormal(an, bn, cn, dn);
        QCOMPARE(data.count(), 4);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Position) | QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.vertices().count(), 4);
        QCOMPARE(data.vertices().at(0), a);
        QCOMPARE(data.vertices().at(1), b);
        QCOMPARE(data.vertices().at(2), c);
        QCOMPARE(data.vertices().at(3), d);
        QCOMPARE(data.normals().count(), 4);
        QCOMPARE(data.normals().at(0), an);
        QCOMPARE(data.normals().at(1), bn);
        QCOMPARE(data.normals().at(2), cn);
        QCOMPARE(data.normals().at(3), dn);
    }
    {
        QGeometryData data;
        data.appendVertex(a, b, c, d);
        data.appendNormal(an, bn, cn, dn);
        data.appendVertex(a, b, c, d);
        data.appendNormal(an, bn, cn, dn);
        data.appendVertex(a);
        data.appendNormal(an);
        QCOMPARE(data.count(), 9);
        QCOMPARE(data.fields(), QGL::fieldMask(QGL::Position) | QGL::fieldMask(QGL::Normal));
        QCOMPARE(data.vertices().count(), 9);
        QCOMPARE(data.vertices().at(0), a);
        QCOMPARE(data.vertices().at(1), b);
        QCOMPARE(data.vertices().at(5), b);
        QCOMPARE(data.vertices().at(8), a);
        QCOMPARE(data.normals().count(), 9);
        QCOMPARE(data.normals().at(0), an);
        QCOMPARE(data.normals().at(1), bn);
        QCOMPARE(data.normals().at(5), bn);
        QCOMPARE(data.normals().at(8), an);
    }
}
예제 #4
0
파일: ply_loader.cpp 프로젝트: acfr/snark
PlyLoader::PlyLoader( const std::string& file, boost::optional< QColor4ub > color, double scale ) : color_( color ), scale_( scale )
{
    std::ifstream stream( file.c_str() );
    std::string line;
    std::getline( stream, line );
    if( line != "ply" ) { COMMA_THROW( comma::exception, "expected ply file; got \"" << line << "\" in " << file ); }
    unsigned int numVertex = 0;
    unsigned int numFace = 0;
    bool has_normals = false;

    std::vector< std::string > fields;
    while( stream.good() && !stream.eof() && line != "end_header" )
    {
        std::getline( stream, line );
        if( line.empty() ) { continue; }
        std::vector< std::string > v = comma::split( comma::strip( line ), ' ' );
        if( v[0] == "element" ) // quick and dirty
        {
            if( v[1] == "vertex" ) { numVertex = boost::lexical_cast< unsigned int >( v[2] ); }
            else if( v[1] == "face" ) { numFace = boost::lexical_cast< unsigned int >( v[2] ); }
        }
        else if( v[0] == "format" && v[1] != "ascii" ) { COMMA_THROW( comma::exception, "only ascii supported; got: " << v[1] ); }
        else if( line == "property float x" ) { fields.push_back( "point/x" ); }
        else if( line == "property float y" ) { fields.push_back( "point/y" ); }
        else if( line == "property float z" ) { fields.push_back( "point/z" ); }
        else if( line == "property float nx" ) { fields.push_back( "normal/x" ); has_normals = true; }
        else if( line == "property float ny" ) { fields.push_back( "normal/y" ); }
        else if( line == "property float nz" ) { fields.push_back( "normal/z" ); }
        else if( line == "property uchar red" ) { fields.push_back( "r" ); }
        else if( line == "property uchar green" ) { fields.push_back( "g" ); }
        else if( line == "property uchar blue" ) { fields.push_back( "b" ); }
        else if( line == "property uchar alpha" ) { fields.push_back( "a" ); }
    }
    comma::csv::options csv;
    csv.fields = comma::join( fields, ',' );
    csv.full_xpath = true;
    csv.delimiter = ' ';
    comma::csv::ascii< ply_vertex > ascii( csv );
    QGeometryData geometry;
    QArray< QVector3D > vertices;
    QArray< QColor4ub > colors;

    for( unsigned int i = 0; i < numVertex; i++ )
    {
        std::string s;
        if( stream.eof() ) { break; }
        std::getline( stream, s );
        if( s.empty() ) { continue; }
        ply_vertex v;
        if( color_ ) { v.color = *color_; } // quick and dirty
        ascii.get( v, s );
        if( numFace > 0 )
        {
            geometry.appendVertex( QVector3D( v.point.x() * scale_, v.point.y() * scale_, v.point.z() * scale_ ) );
            if( has_normals ) { geometry.appendNormal( QVector3D( v.normal.x(), v.normal.y(), v.normal.z() ) ); }
            geometry.appendColor( v.color );
        }
        else
        {
            vertices.append( QVector3D( v.point.x() * scale_, v.point.y() * scale_, v.point.z() * scale_ ) );
            // todo: normals?
            colors.append( v.color );
        }
    }
    if( numFace > 0 )
    {
        for( unsigned int i = 0; i < numFace; i++ ) // quick and dirty
        {
            std::string s;
            if( stream.eof() ) { break; }
            std::getline( stream, s );
            if( s.empty() ) { continue; }
            std::vector< std::string > v = comma::split( comma::strip( s ), ' ' );
            unsigned int vertices_per_face = boost::lexical_cast< unsigned int >( v[0] );
            if( ( vertices_per_face + 1 ) != v.size() ) { COMMA_THROW( comma::exception, "invalid line \"" << s << "\"" ); }
            QGL::IndexArray indices;
            switch( vertices_per_face )
            {
                case 3:
                    for( unsigned int i = 0; i < 3; ++i ) { indices.append( boost::lexical_cast< unsigned int >( v[i+1] ) ); }
                    break;
                case 4: // quick and dirty for now: triangulate
                    boost::array< unsigned int, 4 > a;
                    for( unsigned int i = 0; i < 4; ++i ) { a[i] = boost::lexical_cast< unsigned int >( v[i+1] ); }
                    indices.append( a[0] );
                    indices.append( a[1] );
                    indices.append( a[2] );
                    indices.append( a[0] );
                    indices.append( a[2] );
                    indices.append( a[3] );
                    break;
                default: // never here
                    break;
            }
            geometry.appendIndices( indices );
        }
        QGLBuilder builder;
        builder.addTriangles( geometry );
        //switch( vertices_per_face )
        //{
        //    case 3: builder.addTriangles( geometry ); break;
        //   case 4: builder.addQuads( geometry ); break;
        //  default: COMMA_THROW( comma::exception, "only triangles and quads supported; but got " << vertices_per_face << " vertices per face" );
        //}
        m_sceneNode = builder.finalizedSceneNode();
    }
    else
    {
        m_vertices.addAttribute( QGL::Position, vertices );
        // todo: normals?
        m_vertices.addAttribute( QGL::Color, colors );
        m_vertices.upload();
        m_sceneNode = NULL;
    }
    stream.close();
}
예제 #5
0
QGeometryData MgGeometriesData::sphere(qreal radius,int divisions)
{
	QGeometryData geometry;

	// Determine the number of slices and stacks to generate.
	static int const slicesAndStacks[] = {
			4, 4,
			8, 4,
			8, 8,
			16, 8,
			16, 16,
			32, 16,
			32, 32,
			64, 32,
			64, 64,
			128, 64,
			128, 128
	};
	if (divisions < 1)
		divisions = 1;
	else if (divisions > 10)
		divisions = 10;
	int stacks = slicesAndStacks[divisions * 2 - 1];
	int slices = slicesAndStacks[divisions * 2 - 2];

	// Precompute sin/cos values for the slices and stacks.
	const int maxSlices = 128 + 1;
	const int maxStacks = 128 + 1;
	qreal sliceSin[maxSlices];
	qreal sliceCos[maxSlices];
	qreal stackSin[maxStacks];
	qreal stackCos[maxStacks];
	for (int slice = 0; slice < slices; ++slice)
	{
		qreal angle = 2 * M_PI * slice / slices;
		sliceSin[slice] = qFastSin(angle);
		sliceCos[slice] = qFastCos(angle);
	}
	sliceSin[slices] = sliceSin[0]; // Join first and last slice.
	sliceCos[slices] = sliceCos[0];

	for (int stack = 0; stack <= stacks; ++stack)
	{
		qreal angle = M_PI * stack / stacks;
		stackSin[stack] = qFastSin(angle);
		stackCos[stack] = qFastCos(angle);
	}
	stackSin[0] = 0.0f;             // Come to a point at the poles.
	stackSin[stacks] = 0.0f;

	// Create the stacks.
	for (int stack = 0; stack < stacks; ++stack)
	{
		QGeometryData prim;
		qreal z = radius * stackCos[stack];
		qreal nextz = radius * stackCos[stack + 1];
		qreal s = stackSin[stack];
		qreal nexts = stackSin[stack + 1];
		qreal c = stackCos[stack];
		qreal nextc = stackCos[stack + 1];
		qreal r = radius * s;
		qreal nextr = radius * nexts;
		for (int slice = 0; slice <= slices; ++slice)
		{
			prim.appendVertex
			(QVector3D(nextr * sliceSin[slice],
					nextr * sliceCos[slice], nextz));
			prim.appendNormal
			(QVector3D(sliceSin[slice] * nexts,
					sliceCos[slice] * nexts, nextc));

			prim.appendVertex
			(QVector3D(r * sliceSin[slice],
					r * sliceCos[slice], z));
			prim.appendNormal
			(QVector3D(sliceSin[slice] * s,
					sliceCos[slice] * s, c));
		}
		geometry.appendGeometry(prim);
	}
	return geometry;
}
예제 #6
0
/*!
    \relates QGLSphere

    Builds the geometry for \a sphere within the specified
    display \a list.
*/
QGLBuilder& operator<<(QGLBuilder& list, const QGLSphere& sphere)
{
    qreal scale = sphere.diameter();
    int divisions = qMax(sphere.subdivisionDepth() - 1, 0);

    // define a 0 division sphere as 4 points around the equator, 4 points around the bisection at poles.
    // each division doubles the number of points.
    // since each pass of each loop does half a sphere, we multiply by 2 rather than 4.
    int total = 2*(1 << divisions);

    //list.begin(QGL::TRIANGLE);
    //QGeometryData *prim = list.currentPrimitive();
    QGeometryData prim;

    const QVector3D initialVector(0, 0, 1);
    const QVector3D zAxis(0, 0, 1);
    const QVector3D yAxis(0, 1, 0);
    for(int vindex = 0; vindex < total; vindex++) {
        qreal vFrom = qreal(vindex) / qreal(total);
        qreal vTo = qreal(vindex+1) / qreal(total);
        QQuaternion ryFrom = QQuaternion::fromAxisAndAngle(yAxis, 180.0f * vFrom);
        QQuaternion ryTo = QQuaternion::fromAxisAndAngle(yAxis, 180.0f * vTo);
        for (int uindex = 0; uindex < 2*total; uindex++) {
            qreal uFrom = qreal(uindex) / qreal(total);
            qreal uTo = qreal(uindex+1) / qreal(total);
            QQuaternion rzFrom = QQuaternion::fromAxisAndAngle(zAxis, 180.0f * uFrom);
            QQuaternion rzTo = QQuaternion::fromAxisAndAngle(zAxis, 180.0f * uTo);
            // four points
            QVector3D na, nb, nc, nd;
            QVector3D va, vb, vc, vd;

            na = ryFrom.rotatedVector(initialVector);
            na = rzFrom.rotatedVector(na);
            
            nb = ryTo.rotatedVector(initialVector);
            nb = rzFrom.rotatedVector(nb);

            nc = ryTo.rotatedVector(initialVector);
            nc = rzTo.rotatedVector(nc);

            nd = ryFrom.rotatedVector(initialVector);
            nd = rzTo.rotatedVector(nd);

            QVector2D ta(uFrom/2.0f, 1.0-vFrom);
            QVector2D tb(uFrom/2.0f, 1.0-vTo);
            QVector2D tc(uTo/2.0f, 1.0-vTo);
            QVector2D td(uTo/2.0f, 1.0-vFrom);

            va = na * scale / 2.0f;
            vb = nb * scale / 2.0f;
            vc = nc * scale / 2.0f;
            vd = nd * scale / 2.0f;

            prim.appendVertex(va, vb, vc);
            prim.appendNormal(na, nb, nc);
            prim.appendTexCoord(ta, tb, tc);

            prim.appendVertex(va, vc, vd);
            prim.appendNormal(na, nc, nd);
            prim.appendTexCoord(ta, tc, td);
        }
    }

    list.addTriangles(prim);
    return list;
}
예제 #7
0
//------------------------------------------------------------------------------
QGLBuilder&  operator << ( QGLBuilder& builder, const QGLEllipsoid& ellipsoid )
{
   // Determine the number of slices and stacks to generate.
   static int const numberOfSlicesForSubdivisionDepth[] = { 8, 8, 16, 16, 32, 32, 64, 64, 128, 128 };
   static int const numberOfStacksForSubdivisionDepth[] = { 4, 8,  8, 16, 16, 32, 32, 64,  64, 128 };
   const unsigned int numberOfSlices = numberOfSlicesForSubdivisionDepth[ ellipsoid.GetSubdivisionDepth() - 1 ];
   const unsigned int numberOfStacks = numberOfStacksForSubdivisionDepth[ ellipsoid.GetSubdivisionDepth() - 1 ];

   // Precompute sin/cos values for the slices.
   const unsigned int maxSlices = 128 + 1;
   const unsigned int maxStacks = 128 + 1;
   qreal sliceSin[ maxSlices ];
   qreal sliceCos[ maxSlices ];
   for( unsigned int slice = 0;  slice < numberOfSlices;  ++slice )
   {
       const qreal angle = 2 * M_PI * (numberOfSlices - 1 - slice) / numberOfSlices;
       sliceSin[slice] = qFastSin(angle);
       sliceCos[slice] = qFastCos(angle);
   }
   // Join first and last slice.
   sliceSin[numberOfSlices] = sliceSin[0];
   sliceCos[numberOfSlices] = sliceCos[0];


   // Precompute sin/cos values for the stacks.
   qreal stackSin[ maxStacks ];
   qreal stackCos[ maxStacks ];
   for( unsigned int stack = 0;  stack <= numberOfStacks;  ++stack )
   {
       // Efficiently handle end-points which also ensure geometry comes to a point at the poles (no round-off).
       if(      stack == 0 )               { stackSin[stack] = 0.0f;  stackCos[stack] =  1.0f; }
       else if( stack == numberOfStacks )  { stackSin[stack] = 0.0f;  stackCos[stack] = -1.0f; }
       else
       {
          const qreal angle = M_PI * stack / numberOfStacks;
          stackSin[stack] = qFastSin(angle);
          stackCos[stack] = qFastCos(angle);
       }
   }

   // Half the dimensions of the ellipsoid for calculations below (centroid of ellipsoid is 0, 0, 0.)
   const qreal xRadius = 0.5 * ellipsoid.GetXDiameter();
   const qreal yRadius = 0.5 * ellipsoid.GetYDiameter();
   const qreal zRadius = 0.5 * ellipsoid.GetZDiameter();
   const qreal oneOverXRadiusSquared = 1.0 / (xRadius * xRadius);
   const qreal oneOverYRadiusSquared = 1.0 / (yRadius * yRadius);
   const qreal oneOverZRadiusSquared = 1.0 / (zRadius * zRadius);


   // Create the stacks.
   for( unsigned int stack = 0;  stack < numberOfStacks;  ++stack )
   {
      QGeometryData quadStrip;
      for( unsigned int slice = 0;  slice <= numberOfSlices; ++slice )
      {
          // Equation for ellipsoid surface is x^2/xRadius^2 + y^2/yRadius^2 + z^2/zRadius^2 = 1
          // Location of vertices can be specified in terms of "polar coordinates".
          const qreal nextx = xRadius * stackSin[stack+1] * sliceSin[slice];
          const qreal nexty = yRadius * stackSin[stack+1] * sliceCos[slice];
          const qreal nextz = zRadius * stackCos[stack+1];
          quadStrip.appendVertex( QVector3D( nextx, nexty, nextz) );

          // Equation for ellipsoid surface is  Surface = x^2/xRadius^2 + y^2/yRadius^2 + z^2/zRadius^2 - 1
          // Gradient for ellipsoid is  x/xRadius^2*Nx>  +  y/yRadius^2*Ny>  +  z/zRadius^2*Nz>
          // Gradient for sphere simplifies to  x*Nx> + y*Ny> + z*Nz>
          // const qreal nextGradientx =  stackSin[stack+1] * sliceSin[slice];
          // const qreal nextGradienty =  stackSin[stack+1] * sliceCos[slice];
          // const qreal nextGradientz =  stackCos[stack+1];
          const qreal nextGradientx = nextx * oneOverXRadiusSquared;
          const qreal nextGradienty = nexty * oneOverYRadiusSquared;
          const qreal nextGradientz = nextz * oneOverZRadiusSquared;
          const qreal nextGradientMagSquared = nextGradientx * nextGradientx + nextGradienty * nextGradienty + nextGradientz * nextGradientz;
          const qreal oneOverNextGradientMagnitude = 1.0 / sqrt( nextGradientMagSquared );
          quadStrip.appendNormal( oneOverNextGradientMagnitude * QVector3D( nextGradientx,  nextGradienty, nextGradientz ) );
          quadStrip.appendTexCoord( QVector2D(1.0f - qreal(slice) / numberOfSlices, 1.0f - qreal(stack + 1) / numberOfStacks) );

          const qreal x = xRadius * stackSin[stack] * sliceSin[slice];
          const qreal y = yRadius * stackSin[stack] * sliceCos[slice];
          const qreal z = zRadius * stackCos[stack];
          quadStrip.appendVertex( QVector3D( x, y, z) );

          // const qreal gradientx =  stackSin[stack] * sliceSin[slice];
          // const qreal gradienty =  stackSin[stack] * sliceCos[slice];
          // const qreal gradientz =  stackCos[stack];
          const qreal gradientx = x * oneOverXRadiusSquared;
          const qreal gradienty = y * oneOverYRadiusSquared;
          const qreal gradientz = z * oneOverZRadiusSquared;
          const qreal gradientMagSquared = gradientx * gradientx + gradienty * gradienty + gradientz * gradientz;
          const qreal oneOverGradientMagnitude = 1.0 / sqrt( gradientMagSquared );
          quadStrip.appendNormal( oneOverGradientMagnitude * QVector3D(  gradientx, gradienty, gradientz) );
          quadStrip.appendTexCoord( QVector2D(1.0f - qreal(slice) / numberOfSlices,  1.0f - qreal(stack) / numberOfStacks) );
      }

      // The quad strip stretches from pole to pole.
      builder.addQuadStrip( quadStrip );
   }


   return builder;
}