3
$\begingroup$

I have drawn a cylinder and an icosahedron using OpenGL.

Now, I need to use the icosahedron and the open cylinder to build a structure motivated by crystal lattices, where I use an icosahedron for each atom and a cylinder to connect each 2 icosahedrons.

I would have to use 12 icosahedrons for atoms and about 20 cylinders for the lines connecting the atoms, as I need to place them all to form the shape of an icosahedron itself.

Is there anybody that could give me any ideas regarding how to go about this? I can arrange 12 icosahedrons to simulate the 12 vertices of an icosahedron, but I'm not sure how to place all of the cylinders around properly.

$\endgroup$
1
  • $\begingroup$ billboards, flat squares rotated to always face the camera $\endgroup$ Commented Feb 23, 2017 at 8:13

2 Answers 2

1
$\begingroup$

When drawing in OpenGL and related APIs you usually have one or more models. In your case, this would be the cylinder and the icosahedron. The models are generally generated at some default size which may or may not suit your needs for a particular task. You move them, scale them, and rotate them by setting up a model matrix which describes how you want to transform them.

So how would this work in your case? I recommend having 2 functions. One which draws a unit cylinder and another which draws a unit icosahedron. Say you have a data structure for your coordinates like this (I'm assuming a C-like language):

typedef struct Position3D {
    float x;
    float y;
    float z;
} Position3D;

You can then construct a triangle by making a structure for one:

typedef struct Triangle3D {
    Position3D p0;
    Position3D p1;
    Position3D p2;
} Triangle3D;

Then you'd have functions for generating the vertices you need. Something like this:

void generateCylinder (Triangle3D* triangles) // <-assumes you allocated triangles before calling and that you have enough space for all of them!
{
    int i = 0;
    const float kRadius = 1.0;
    const float kDeltaH = 0.1; // <- change this to make the mesh more or less dense
    const float kDeltaAngle = M_PI / 10.0; // <- change this to make the mesh more or less curved
    for (float h = -1.0; h < 1.0; h += kDeltah)
    {
        for (float angle = 0; angle < 2.0 * M_PI; angle += kDeltaAngle)
        {
            float x = kRadius * cos(angle);
            float y = h;
            float z = kRadius * sin(angle);
            //... add points to triangles array here...
            // p0 = <x,y,z>
            // p1 = <x, y + kDeltaH, z>
            // p1 = <x + radius * cos(angle + kDeltaAngle), y, z  + radius * sin(angle + kDeltaAngle)>
        }
    }
}

And a similar function for the icosahedron.

Next, you'll create a matrix for every object you draw and send that to your shader before drawing it. So you'll have the translations for the vertices of the (big) icosahedron, and you want to draw a (small) icosahedron at each one. You'd do the following:

float translationMatrix [ 16 ] = {
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
};
translationMatrix [ 12 ] = xPos;
translationMatrix [ 13 ] = yPos;
translationMatrix [ 14 ] = zPos;
GLuint modelMatrixLoc = glGetUniformLocation(program, "modelMatrix");
glUniformMatrix4fv(modelMatrixLoc, 1, GL_FALSE, translationMatrix);

Then you'd draw your icosahedron. For the cylinders you would need to scale vertically, rotate to the appropriate angle and translate it to the position between 2 icosahedrons.

$\endgroup$
1
$\begingroup$

You could calculate the midpoints of the segments between the vertices (atoms) of the icosahedron. These are the positions of the cylinders. You would then need to determine the orientation and scale of the cylinders. This information should then be encoded into a transformation matrix. Doing this for every cylinder would mean multiple transformation matrices.

An effective way to draw many copies of the same object is to use instanced drawing. Instead of defining the position of the vertices of each cylinder separately, you only define the geometry for one cylinder and send this to a buffer. You can then draw a number of copies of this cylinder and transforming each copy of this cylinder with its respective transformation matrix. The matrices should be put into a buffer on the GPU to make it efficient. Alternatively, it is possible to do it with uniforms but this would mean multiple draw calls and no instancing.

A couple of things you would need to look up documentation for is instanced drawing. Next, would be to investigate attribute divisors. Attribute divisors regulate the rate for which vertex attributes are read from the buffer. If you upload the transformation matrices for each cylinder with 40 vertices to the buffer. You would want to have one transformation matrix for each batch of 40 vertices. Therefore, the attribute divisor for the transformation matrix should be set to 1, such that it advances once per batch of vertices.

Using this approach it is best to define you cylinder as an axis aligned unit length and width cylinder centered at the origin. This will simplify the transformations a whole lot.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.