Creating a Sphere (using osg::Geometry) in OpenSceneGraph
Asked Answered
D

3

12

I spent quite some time to get this working, but my Sphere just won't display.
Used the following code to make my function:
Creating a 3D sphere in Opengl using Visual C++

And the rest is simple OSG with osg::Geometry.
(Note: Not ShapeDrawable, as you can't implement custom shapes using that.)
Added the vertices, normals, texcoords into VecArrays.

For one, I suspect something misbehaving, as my saved object is half empty.
Is there a way to convert the existing description into OSG?
Reason? I want to understand how to create objects later on.
Indeed, it is linked with a later assignment, but currently I'm just prepairing beforehand.

Sidenote: Since I have to make it without indices, I left them out.
But my cylinder displays just fine without them.

Distract answered 6/12, 2013 at 20:56 Comment(7)
Hi any chance you could post a small sample of code? It would help us to help you:)Pelvis
@Pelvis I thought of it, but then I dropped the idea as OSG got a really simple syntax. I'll add some code tomorrow.Distract
Were you going to share some code with us?Preschool
@JoeZ - Here is a sample. trac.openscenegraph.org/projects/osg//wiki/Support/Tutorials/… (I just made a function that pushed data into the arrays, and then displayed the quad, the big nothing.)Distract
@Shiki: Did you get a chance to take a look at and try my updated code?Preschool
@JoeZ: I'll take a look at it quite soon. I'll restart the bounty as soon as it gets down. By now I realize I put up the question way too soon, since then I got busy and had no time to spend time with it. But I will, and sorry everyone for not being able to provide feedback.Distract
For everyone who is interested: Found a great way to generate shapes, simply by using their parametric equations. I'll post my code as my own answer. Hope it will help people learn something new.Distract
P
6

Caveat: I'm not an OSG expert. But, I did do some research.

OSG requires all of the faces to be defined in counter-clockwise order, so that backface culling can reject faces that are "facing away". The code you're using to generate the sphere does not generate all the faces in counter-clockwise order.

You can approach this a couple ways:

  1. Adjust how the code generates the faces, by inserting the faces CCW order.
  2. Double up your model and insert each face twice, once with the vertices on each face in their current order and once with the vertices in reverse order.

Option 1 above will limit your total polygon count to what's needed. Option 2 will give you a sphere that's visible from outside the sphere as well as within.

To implement Option 2, you merely need to modify this loop from the code you linked to:

    indices.resize(rings * sectors * 4);
    std::vector<GLushort>::iterator i = indices.begin();
    for(r = 0; r < rings-1; r++) 
        for(s = 0; s < sectors-1; s++) {
            *i++ = r * sectors + s;
            *i++ = r * sectors + (s+1);
            *i++ = (r+1) * sectors + (s+1);
            *i++ = (r+1) * sectors + s;
        }

Double up the set of quads like so:

    indices.resize(rings * sectors * 8);
    std::vector<GLushort>::iterator i = indices.begin();
    for(r = 0; r < rings-1; r++) 
        for(s = 0; s < sectors-1; s++) {
            *i++ = r * sectors + s;
            *i++ = r * sectors + (s+1);
            *i++ = (r+1) * sectors + (s+1);
            *i++ = (r+1) * sectors + s;

            *i++ = (r+1) * sectors + s;
            *i++ = (r+1) * sectors + (s+1);
            *i++ = r * sectors + (s+1);
            *i++ = r * sectors + s;
        }

That really is the "bigger hammer" solution, though.

Personally, I'm having a hard time figuring out why the original loop isn't sufficient; intuiting my way through the geometry, it feels like it's already generating CCW faces, because each successive ring is above the previous, and each successive sector is CCW around the surface of the sphere from the previous. So, the original order itself should be CCW with respect to the face nearest the viewer.


EDIT Using the OpenGL code you linked before and the OSG tutorial you linked today, I put together what I think is a correct program to generate the osg::Geometry / osg::Geode for the sphere. I have no way to test the following code, but desk-checking it, it looks correct or at least largely correct.

#include <vector>

class SolidSphere
{
protected:

    osg::Geode      sphereGeode;
    osg::Geometry   sphereGeometry;
    osg::Vec3Array  sphereVertices;
    osg::Vec3Array  sphereNormals;
    osg::Vec2Array  sphereTexCoords;

    std::vector<osg::DrawElementsUInt> spherePrimitiveSets;

public:
    SolidSphere(float radius, unsigned int rings, unsigned int sectors)
    {
        float const R = 1./(float)(rings-1);
        float const S = 1./(float)(sectors-1);
        int r, s;

        sphereGeode.addDrawable( &sphereGeometry );

        // Establish texture coordinates, vertex list, and normals
        for(r = 0; r < rings; r++)
            for(s = 0; s < sectors; s++)
            {
                float const y = sin( -M_PI_2 + M_PI * r * R );
                float const x = cos(2*M_PI * s * S) * sin( M_PI * r * R );
                float const z = sin(2*M_PI * s * S) * sin( M_PI * r * R );

                sphereTexCoords.push_back( osg::Vec2(s*R, r*R) );

                sphereVertices.push_back ( osg::Vec3(x * radius,
                                                     y * radius,
                                                     z * radius) );

                sphereNormals.push_back  ( osg::Vec3(x, y, z) );

            }

        sphereGeometry.setVertexArray  ( &spehreVertices  );
        sphereGeometry.setTexCoordArray( &sphereTexCoords );

        // Generate quads for each face.  
        for(r = 0; r < rings-1; r++)
            for(s = 0; s < sectors-1; s++)
            {
                spherePrimitiveSets.push_back(
                    DrawElementUint( osg::PrimitiveSet::QUADS, 0 )
                );

                osg::DrawElementsUInt& face = spherePrimitiveSets.back();

                // Corners of quads should be in CCW order.
                face.push_back( (r + 0) * sectors + (s + 0) );
                face.push_back( (r + 0) * sectors + (s + 1) );
                face.push_back( (r + 1) * sectors + (s + 1) );
                face.push_back( (r + 1) * sectors + (s + 0) );

                sphereGeometry.addPrimitveSet( &face );
            }
    }

    osg::Geode     *getGeode()     const { return &sphereGeode;     }
    osg::Geometry  *getGeometry()  const { return &sphereGeometry;  }
    osg::Vec3Array *getVertices()  const { return &sphereVertices;  }
    osg::Vec3Array *getNormals()   const { return &sphereNormals;   }
    osg::Vec2Array *getTexCoords() const { return &sphereTexCoords; }

};

You can use the getXXX methods to get the various pieces. I didn't see how to hook the surface normals to anything, but I do store them in a Vec2Array. If you have a use for them, they're computed and stored and waiting to be hooked to something.

Preschool answered 11/12, 2013 at 13:30 Comment(17)
The problem with this is that we don't use (AFAIK) indices in OSG. This makes a pyramid for example: trac.openscenegraph.org/projects/osg//wiki/Support/Tutorials/… . This shape also relies only on vertices... How do I into shapes?Distract
So the thing is, we use Vertices (and Vec3Array for that), so you can't even push 4 indices there either. Maybe I'm totally missing the point here. If so, please explain.Distract
The example shows pushing indices to vertices. That's what pyramidBase->push_back() is doing. They push several vertices, and then they start defining faces in terms of indices into those vertices.Preschool
@Shiki : I've edited my post to provide OSG code. Let me know how it goes.Preschool
This is what the exported .obj looks like: pastebin.com/GVAkLM5n | And here is the half-sphere-ish result. I didn't create a class as that code got even more problems that needs to be fixed. So here it is: i.imgur.com/a0p2fGN.jpgDistract
@Shiki: If I understood the faces in the f lines in the OBJ, those are triangles, not quads. Are you generating quads in your code or do you need triangles? If you need triangles, then you need to push both triangles associated with the quad. It definitely looks like you're misinterpreting quad vertices as triangles. (There are some "bowtie" shapes in there that I think are due to that.)Preschool
@Shiki: Was the OBJ you uploaded complete? It only has 512 faces, and only refers to 1536 vertices, but actually has 3750 vertices. 512 faces seems like a curious cutoff, actually.Preschool
@Shiki: I wrote a quick and dirty perl script to generate proper quad faces for the vertices that were already in your .OBJ file. It appears to have generated a proper sphere. You can see my perl script here: spatula-city.org/~im14u2c/dl/huge_sphere_vertex_gen.txt And you can get the corrected OBJ here: spatula-city.org/~im14u2c/dl/huge_sphere_fixed.txt Something about how you're pushing faces onto your sphere is messed up. What you put on pastebin had triangles, not quads, and the vertex order was just simply wrong.Preschool
@Shiki: ..continuing: You need a loop nest like the final loop nest in my code above that says "Generate quads for each face." And if you need triangles instead of quads, that's not hard to do, but say so if that's the requirement. OSG lets you push quads.Preschool
Alright, I'll get to the code today and will report back. Thanks for spending so much time on the Q/issue.Distract
Well, the code is just not usable, to be honest. I spent a couple more hours on this but it's just not going anywhere. IF you up them they will just won't display as they are on the very same place.Distract
@Shiki How did this go? Is the code ok? I'm asking because I see that you gave the bounty, but didn't accept the answer.Ontogeny
@AdriC.S.: Yeah, sorry about that. Accepted your answer.Distract
@Shiki My answer? You're mistaken! haha. I tried the code, had to fix some errors in it but it worked fine. The sphere it creates is very nice.Ontogeny
@AdriC.S.: I used a totally different approach in the end. Anyway. I just come up to the site to mark answers/delete/report mines. The site gets more and more outdated/obsolete questions day by day. It's almost totally useless nowadays. :/Distract
@Shiki I used the answer's code, but had to fix it. It shows a nice sphere.Ontogeny
@JoeZ I posted an answer with your OSG fixed. Thanks for your answer!!Ontogeny
R
3

That code calls glutSolidSphere() to draw a sphere, but it doesn't make sense to call it if your application is not using GLUT to display a window with 3D context.

There is another way to draw a sphere easily, which is by invoking gluSphere() (you probably have GLU installed):

void gluSphere(GLUquadric* quad, GLdouble radius, GLint slices, GLint stacks);

Parameters

quad - Specifies the quadrics object (created with gluNewQuadric).

radius - Specifies the radius of the sphere.

slices - Specifies the number of subdivisions around the z axis (similar to lines of longitude).

stacks - Specifies the number of subdivisions along the z axis (similar to lines of latitude).

Usage:

// If you also need to include glew.h, do it before glu.h
#include <glu.h>

GLUquadric* _quadratic = gluNewQuadric();
if (_quadratic == NULL)
{
    std::cerr << "!!! Failed gluNewQuadric" << std::endl;
    return;
}

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

glTranslatef(0.0, 0.0, -5.0);
glColor3ub(255, 97, 3);
gluSphere(_quadratic, 1.4f, 64, 64);

glFlush();

gluDeleteQuadric(_quadratic);

It's probably wiser to move the gluNewQuadric() call to the constructor of your class since it needs to be allocated only once, and move the call to gluDeleteQuadric() to the destructor of the class.

Roaster answered 16/12, 2013 at 20:11 Comment(2)
You cannot export such geometric in OSG (as far as I know), and the question is about pure (as pure as it gets) OSG. Nevertheless, the answer is quite useful for those who may wander to the lands of OSG.Distract
You can have GLU stuff in OSG by inheriting from osg::Drawable and overriding the drawImplementation() method. Actually you can have whatever GL stuff you want in there. This done in the NURBS Surface example in OpenSceneGraph 3 Cookbook. It is really slow however, in that particular case.Undesirable
O
1

@JoeZ's answer is excellent, but the OSG code has some errors/bad practices. Here's the updated code. It's been tested and it shows a very nice sphere.

    osg::ref_ptr<osg::Geode> buildSphere( const double radius,
                                          const unsigned int rings,
                                          const unsigned int sectors )
    {
        osg::ref_ptr<osg::Geode>      sphereGeode = new osg::Geode;
        osg::ref_ptr<osg::Geometry>   sphereGeometry = new osg::Geometry;
        osg::ref_ptr<osg::Vec3Array>  sphereVertices = new osg::Vec3Array;
        osg::ref_ptr<osg::Vec3Array>  sphereNormals = new osg::Vec3Array;
        osg::ref_ptr<osg::Vec2Array>  sphereTexCoords = new osg::Vec2Array;

        float const R = 1. / static_cast<float>( rings - 1 );
        float const S = 1. / static_cast<float>( sectors - 1 );

        sphereGeode->addDrawable( sphereGeometry );

        // Establish texture coordinates, vertex list, and normals
        for( unsigned int r( 0 ); r < rings; ++r ) {
            for( unsigned int s( 0) ; s < sectors; ++s ) {
                float const y = sin( -M_PI_2 + M_PI * r * R );
                float const x = cos( 2 * M_PI * s * S) * sin( M_PI * r * R );
                float const z = sin( 2 * M_PI * s * S) * sin( M_PI * r * R );

                sphereTexCoords->push_back( osg::Vec2( s * R, r * R ) );

                sphereVertices->push_back ( osg::Vec3( x * radius,
                                                       y * radius,
                                                       z * radius) )
                ;
                sphereNormals->push_back  ( osg::Vec3( x, y, z ) );

            }
        }

        sphereGeometry->setVertexArray  ( sphereVertices  );
        sphereGeometry->setTexCoordArray( 0, sphereTexCoords );

        // Generate quads for each face.
        for( unsigned int r( 0 ); r < rings - 1; ++r ) {
            for( unsigned int s( 0 ); s < sectors - 1; ++s ) {

                osg::ref_ptr<osg::DrawElementsUInt> face =
                        new osg::DrawElementsUInt( osg::PrimitiveSet::QUADS,
                                                   4 )
                ;
                // Corners of quads should be in CCW order.
                face->push_back( ( r + 0 ) * sectors + ( s + 0 ) );
                face->push_back( ( r + 0 ) * sectors + ( s + 1 ) );
                face->push_back( ( r + 1 ) * sectors + ( s + 1 ) );
                face->push_back( ( r + 1 ) * sectors + ( s + 0 ) );

                sphereGeometry->addPrimitiveSet( face );
            }
        }

        return sphereGeode;
    }

Changes:

  • The OSG elements used in the code now are smart pointers1. Moreover, classes like Geode and Geometry have their destructors protected, so the only way to instantiate them are via dynamic allocation.

  • Removed spherePrimitiveSets as it isn't needed in the current version of the code.

  • I put the code in a free function, as I don't need a Sphere class in my code. I omitted the getters and the protected attributes. They aren't needed: if you need to access, say, the geometry, you can get it via: sphereGeode->getDrawable(...). The same goes for the rest of the attributes.

[1] See Rule of thumb #1 here. It's a bit old but the advice maintains.

Ontogeny answered 3/11, 2014 at 8:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.