8
$\begingroup$

It seems that blender python exposes very limited functionality of OpenGL. I currently want to draw additional information in every edge of a mesh by using OpenGL. Basically the edge direction is what I am looking for. However, I am not getting very good performance for a mesh consisting of 59,620 edges.

Initially I tried opengl drawing using immediate mode, this was too slow. Next I tried using display list. It is good but not what I expected. I'd have to manually update every the display list when there is a change made(adding, deleting edge/vertex/face). Is there any alternative way to do this? Or is there some way I can just get edges displayed on screen/viewport and display properties for them. That way I don't have to go through checking every edges.

I have a checkbox which when enabled simply generates a display list and adds a draw_handler to the view3d scene which calls this display list.

enter image description here

enter image description here

EDIT: First I create the opengl display list using this piece of code. This code simply goes through every edge and creates an arrow representing the edge direction and finally generates the opengl display list.

    def createArrowDisplayList(self, context):
        ob = context.object
        if ob is None:
            return

        if bpy.context.active_object.mode != 'EDIT':
            return

        ob = context.edit_object
        me = ob.data

        bm = bmesh.from_edit_mesh(me)
        index = glGenLists(1)

        glNewList(index, GL_COMPILE)
        glColor3f(0.0,1.0,1.0)
        for e in bm.edges:
            vecFrom = e.verts[0].co
            vecTo = e.verts[1].co

            middle = (Vector(vecTo) + Vector(vecFrom)) / 2.0

            v = Vector(vecTo) - Vector(vecFrom)
            v.normalize()

            # if vector is straight pointing up only on z axis ignore it
            if abs(v.x) < 0.0001 and abs(v.y) < 0.0001:
                continue

            vPerp1 = Vector((-v.y, v.x, 0.0))
            vPerp2 = Vector((v.y, -v.x, 0.0))

            v1 = (vPerp1 - v).normalized()
            v2 = (vPerp2 - v).normalized()

            SCALER = 1.0

            glPushMatrix()
            hAngle = degrees(v.xy.angle_signed(Vector((0,1))))
            vAngle = -degrees(v.angle(v.xy.to_3d()))
            glTranslatef(*middle)
            glRotatef(hAngle, 0.0, 0.0, 1.0)
            glRotatef(vAngle, 1.0, 0.0, 0.0)
            glScalef(SCALER, SCALER, SCALER)

            glBegin(GL_TRIANGLES)
            glVertex3f( -0.5, -1.0, 0.0 )      
            glVertex3f( 0.0, 1.0, 0.0 )
            glVertex3f( 0.5, -1.0, 0.0 )
            glEnd()
            glPopMatrix()


            """
            glBegin(GL_LINE_STRIP)
            glVertex3f(*(middle + v1))
            glVertex3f(*(middle))
            glVertex3f(*(middle + v2))
            glEnd()
            """

        glEndList()
        return index

And then, I add this method to the draw handler on 3D view. Now the display list needs to be refreshed or generated again manually whenever there is any change made to the underlying mesh

    @staticmethod
    def draw_callback_px(context, displayListIndex):
        wm = context.window_manager
        if wm.mesh_layer.bDisplayEdgeDirection == False:
            return

        ob = context.object
        if ob is None:
            return

        # 50% alpha, 2 pixel width line
        #glEnable(GL_BLEND)
        glColor4f(1.0, 1.0, 1.0, 0.5)
        #glLineWidth(2)

        glPushMatrix()
        glScalef(*ob.scale)
        glTranslatef(*ob.location)
        glCallList(displayListIndex)
        glPopMatrix()

        # restore opengl defaults
        glLineWidth(1)
        glDisable(GL_BLEND)
        glColor4f(0.0, 0.0, 0.0, 1.0)
$\endgroup$
  • $\begingroup$ " display list. It is good but not what I expected " -- can you elaborate? we use a display list in Sverchok and it performs quite well if the view is rotated, but the initial geometry caching call is still about as slow as immediate mode. (which I think is reasonable to expect) $\endgroup$ – zeffii Sep 6 '15 at 17:22
  • $\begingroup$ vecFrom and vecTo are already Vectors you don't need to recast them $\endgroup$ – zeffii Sep 6 '15 at 17:30
  • $\begingroup$ @zeffii I have added the code that generates the list and the draw_handler that displays them. Hopefully that makes sense $\endgroup$ – Swoorup Sep 6 '15 at 17:30
  • $\begingroup$ @zeffii Its called only once when creating the display list. Should that make a difference? $\endgroup$ – Swoorup Sep 6 '15 at 17:31
  • 1
    $\begingroup$ @zeffii Ok, I can just confirm it. Just directly calculating all the vertex and getting rid of all glPushMatrix, glPopMatrix, glRotate, glTranslate is faster. This is really strange, or maybe bugged driver. $\endgroup$ – Swoorup Sep 6 '15 at 18:01
9
+25
$\begingroup$

As you have noticed, drawing bgl is relative easy. For trivial stuff many settle for immediate mode, because the overhead in their drawing routine is never noticed (either a really fast processor, or simply minimal drawing instructions).

As soon as you crank up the amount of geometry and complexity of the drawing call, you will need to think about optimizations and perhaps use a DisplayList. Optimize the code you were using in immediate mode first. Use timers and reproducible geometric inputs to see what kind of effect your code modifications have and figure out which parts take up the most time overal and concentrate on those, profiling helps.

A DisplayList isn't magically going to make your fresh bgl drawing faster.

  • You already noticed that using the push / pop matrix for 3 vertices can introduce more overhead than calculating them directly.
  • a .co is already a Vector, it doesn't need to be recast as a Vector()
  • I'd try some counter intuitive things like pre-calculating the coordinates for all polygons you intend to draw, then for-loop through them inside GL_TRIANGLES where each 3 new vertices pushed is part of a new Tri.
  • Consider numpy! initial filling of arrays may be the bottle neck, after that you can get a lot of processing done very fast with the right constructs. (becomes off-topic for BSE)

50k plus edges, in your program translates to 50k polygons with 150k vertices, that's a lot of geometry to update/recalcuate with python and bgl at a high frame-rate.

  • It may be worth considering splitting it into subsets, if not all data needs to be updated
  • If it makes sense, show only a sampling of all your points, show more samples in the area you are editing, and less or no samples from beyond it.
  • If it's not visible, does it need to be calculated.
  • does it need to be 3d? it's easy to convert view3d 3d coordinates to 2d screen coordinates, and drop edges if they don't meet some criteria like:
    • the length of the edge on-screen is below a set amount of pixels, don't calculate draw arrow, don't draw it.
    • are both (or just one) coordinates of the edge visible, decide based on this whether you need to draw it.
  • only consider selected edges (this will include the active edge).

Alternatives to bgl

Mesh

For Sverchok we found it was worth experimenting with a non bgl way of displaying mesh data, by generating a Mesh object and let Blender take care of the Drawing! Such a Mesh object can either be wiped and refilled between updates or you can use a foreach_set('co', flatlist) when the geometry of the existing mesh has the same amount of coordinates as the geometry you're trying to replace it with. It might be worth reading the code we settled on: nodes/basic_view/viewer_bmesh_mk2.py -- there's no guarantee this this will be any faster, but it does sidestep home-grown bgl code.

DupliVerts + rotation

It is possible to make a secondary mesh with only the points where these arrows are supposed to appear, and then assign the vertices of the secondary mesh each a custom normal by doing v.normal = orientation. The duplicated Object will then rotate to point in the orientation passed to the normals. Example code: https://gist.github.com/zeffii/d61a4963e6c093dda52b

enter image description here

  • drawback is that if you go into Edit Mode for the duplivert mesh, the normals will reset. (but you probably don't even need to go directly into edit mode for that mesh..)
  • it is possible to use foreach_set('normal', flatlist) on the data.vertices collection which is quite fast.
$\endgroup$
  • $\begingroup$ I have already tried most of the first 4 points. The thing is that the function that creates the Display list is hooked on to a button operator. I actually need to make this real-time or have it generated every frame. The last 3 points are definitely what I want. But I am not sure how to go on proceed doing that. Could you possibly elaborate further? $\endgroup$ – Swoorup Sep 12 '15 at 20:18
  • $\begingroup$ there's the KDTree module which lets you do a fast search for distance -- it's a big question you are posing here. I don't have simple answers for you $\endgroup$ – zeffii Sep 12 '15 at 20:53
  • $\begingroup$ But for KDTree, I'd have to insert vertices each frame, I would actually be drawing this in edit mode. So you can see changes while you are deleting, moving vertices. Is it possible or just far fetched? $\endgroup$ – Swoorup Sep 13 '15 at 22:35
  • 1
    $\begingroup$ Yeah that would be a problem, it seems more reasonable to pick a way to abstract down the useful datapoints to what is visible. Even if you could display all triangles quickly, our eyes only have a small area where all detail is seen en.wikipedia.org/wiki/Fovea_centralis $\endgroup$ – zeffii Sep 14 '15 at 6:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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