Dynamic Mesh using VBO

In some cases you may want to update the geometry of a Mesh entity very often (even at each frame).

Suppose we have a color Mesh whose vertices positions and colors change at each frame: after each data update, the Entity.RegenMode is set to RegenAndCompile and the ViewportLayout.Entities.Regen() needs to be called.

The Eyeshot's Mesh class, that use OpenGL display lists, is not designed to support these kind of "dynamic" entities and can give bad performances.

To improve processing speed, set first:

Mesh.LightWeight = true;

to avoid the computation of the mesh edges at each regeneration. Then use the following Mesh derived class that uses Vertex Buffer Object (VBO), the best approach to draw a Mesh entity that changes so frequently:

        ///   
        /// Class that uses VBOs (vertex buffer objects) for better performances when frequently updating the mesh geometry. 
        ///  
        class VBOMesh : Mesh 
        { 
            public VBOMesh() 
                : base(meshNatureType.Smooth) 
            { 
                base.Triangles = new IndexTriangle[0];
                base.Vertices = new Point3D[0];
            } 

            private int vertexBuffer, indexBuffer;

            int verticesSize, normalsSize, colorsSize, indicesSize;

            public float[] Vertices { get; set; } 
            public float[] Normals { get; set; } 
            public byte[] Colors { get; set; } 
            public int[] Triangles { get; set; } 


            public override void Compile(CompileParams data)
            { 
                // Use Vertex Buffer Objects  

                int count; 

                if (vertexBuffer == 0)
                { 
                    // The first time initialize the buffer objects 

                    // Create the 2 buffer objects  
                    int[] buffer = gl.GenBuffersARB(2); 

                    vertexBuffer = buffer[0]; 
                    indexBuffer = buffer[1]; 

                    indicesSize = Triangles.Length * sizeof(int); 

                    // Bind the element buffer object and copy the indices to it 
                    gl.BindBufferARB(gl.ELEMENT_ARRAY_BUFFER_ARB, indexBuffer); 
                    gl.BufferData(gl.ELEMENT_ARRAY_BUFFER_ARB, indicesSize, Triangles, gl.STATIC_DRAW_ARB);

                    // The normals and colors array must have the same number of elements as the vertices array 
                    verticesSize = Vertices.Length * sizeof(float); 
                    normalsSize = Vertices.Length * sizeof(float); 
                    colorsSize = Vertices.Length * sizeof(byte); 

                    // Bind the array buffer object for the vertices, normals and colors and allocate its size 
                    gl.BindBufferARB(gl.ARRAY_BUFFER_ARB, vertexBuffer); 
                    gl.BufferData(gl.ARRAY_BUFFER_ARB, verticesSize + normalsSize + colorsSize, IntPtr.Zero, gl.STREAM_DRAW_ARB);
                } 

                // Bind the array buffer object and copy the data to it 
                gl.BindBufferARB(gl.ARRAY_BUFFER_ARB, vertexBuffer); 
                gl.BufferSubData(gl.ARRAY_BUFFER_ARB, 0, verticesSize, Vertices);
                gl.BufferSubData(gl.ARRAY_BUFFER_ARB, verticesSize, normalsSize, Normals); 
                gl.BufferSubData(gl.ARRAY_BUFFER_ARB, verticesSize + normalsSize, colorsSize, Colors);

                // Unbind the buffer objects  
                gl.BindBufferARB(gl.ARRAY_BUFFER_ARB, 0); 
                gl.BindBufferARB(gl.ELEMENT_ARRAY_BUFFER_ARB, 0);
            } 

            protected override void DrawIsocurves()
            { 
                DrawSelected(null); 
            } 

            protected override void DrawWireframe(DrawParams drawParams)
            { 
                DrawSelected(drawParams); 
            } 

            protected override void Draw(DrawParams data)
            { 
                Render(null); 
            } 

            protected override void SetShader(HqrData hqrData)
            { 
                if (!Selected) 
                { 
                    bool prev = hqrData.ShaderParams.Multicolor;
                    hqrData.ShaderParams.Multicolor = true; 

                    base.SetShader(hqrData); 

                    hqrData.ShaderParams.Multicolor = prev; 
                } 
            } 

            protected override void Render(RenderParams data)
            { 
                gl.ColorMaterial(gl.FRONT_AND_BACK, gl.DIFFUSE); 
                gl.Enable(gl.COLOR_MATERIAL); 

                // Bind the buffer objects  
                gl.BindBufferARB(gl.ARRAY_BUFFER_ARB, vertexBuffer); 
                gl.BindBufferARB(gl.ELEMENT_ARRAY_BUFFER_ARB, indexBuffer); 

                // Activate the necessary arrays  
                gl.EnableClientState(gl.VERTEX_ARRAY); 
                gl.EnableClientState(gl.NORMAL_ARRAY); 
                gl.EnableClientState(gl.COLOR_ARRAY); 

                // Specify the pointers to vertex data (the last parameter is the offset in the buffer array) 
                gl.VertexPointer(3, gl.FLOAT, 0, 0);
                gl.NormalPointer(gl.FLOAT, 0, verticesSize);
                gl.ColorPointer(3, gl.UNSIGNED_BYTE, 0, verticesSize + normalsSize);

                // Draw the triangles  
                gl.DrawElements(gl.TRIANGLES, Triangles.Length, gl.UNSIGNED_INT, 0);

                // Disable the arrays  
                gl.DisableClientState(gl.VERTEX_ARRAY); 
                gl.DisableClientState(gl.NORMAL_ARRAY); 
                gl.DisableClientState(gl.COLOR_ARRAY); 

                // Unbind the buffer objects  
                gl.BindBufferARB(gl.ARRAY_BUFFER_ARB, 0); 
                gl.BindBufferARB(gl.ELEMENT_ARRAY_BUFFER_ARB, 0);

                gl.Disable(gl.COLOR_MATERIAL); 
            } 

            protected override void DrawForShadow()
            { 
                DrawSelected(null); 
            } 

            protected override void DrawForSelection(DrawParams data)
            { 
                DrawSelected(data); 
            } 

            protected override void DrawSelected(DrawParams drawParams)
            { 
                gl.BindBufferARB(gl.ARRAY_BUFFER_ARB, vertexBuffer); 
                gl.BindBufferARB(gl.ELEMENT_ARRAY_BUFFER_ARB, indexBuffer); 

                // Activate the necessary arrays  
                gl.EnableClientState(gl.VERTEX_ARRAY); 

                // Specify the pointers to vertex data (the last parameter is the offset in the buffer array) 
                gl.VertexPointer(3, gl.FLOAT, 0, 0);

                // Draw the triangles  
                gl.DrawElements(gl.TRIANGLES, Triangles.Length, gl.UNSIGNED_INT, 0);

                // Disable the arrays  
                gl.DisableClientState(gl.VERTEX_ARRAY); 

                // Unbind the buffer objects  
                gl.BindBufferARB(gl.ARRAY_BUFFER_ARB, 0); 
                gl.BindBufferARB(gl.ELEMENT_ARRAY_BUFFER_ARB, 0);

            } 

            public override void Dispose()
            { 
                // Delete the buffer objects  
                gl.DeleteBuffersARB(new int[] { vertexBuffer, indexBuffer }); 

                vertexBuffer = 0; 
                indexBuffer = 0; 
                base.Dispose(); 
            } 

            protected override bool ComputeBoundingBox(TraversalParams data, out Point3D boxMin, out Point3D boxMax)
            { 
                Point3D min = Point3D.MaxValue; 
                Point3D max = Point3D.MinValue; 

                Point3D pt = new Point3D(); 

                if (data.Transformation == null || data.Transformation.IsIdentity())
                { 
                    for (int i = 0; i < Vertices.Length; /**/)
                    { 
                        pt.X = Vertices[i++]; 
                        pt.Y = Vertices[i++]; 
                        pt.Z = Vertices[i++]; 

                        if (min.X > pt.X) 
                            min.X = pt.X; 

                        if (min.Y > pt.Y) 
                            min.Y = pt.Y; 

                        if (min.Z > pt.Z) 
                            min.Z = pt.Z; 

                        if (max.X < pt.X) 
                            max.X = pt.X; 

                        if (max.Y < pt.Y) 
                            max.Y = pt.Y; 

                        if (max.Z < pt.Z) 
                            max.Z = pt.Z; 
                    } 
                } 
                else 
                { 
                    for (int i = 0; i < Vertices.Length; /**/)
                    { 
                        pt.X = Vertices[i++]; 
                        pt.Y = Vertices[i++]; 
                        pt.Z = Vertices[i++]; 

                        pt = data.Transformation * pt; 

                        if (min.X > pt.X) 
                            min.X = pt.X; 

                        if (min.Y > pt.Y) 
                            min.Y = pt.Y; 

                        if (min.Z > pt.Z) 
                            min.Z = pt.Z; 

                        if (max.X < pt.X) 
                            max.X = pt.X; 

                        if (max.Y < pt.Y) 
                            max.Y = pt.Y; 

                        if (max.Z < pt.Z) 
                            max.Z = pt.Z; 
                    } 

                } 

                boxMin = min; 
                boxMax = max; 

                return true;
            } 

            protected override void ComputeOffsetOnCameraAxes(OffsetOnCameraAxesParams data)
            { 
                Point3D pt = new Point3D(); 

                for (int i = 0; i < Vertices.Length; /**/)
                { 
                    pt.X = Vertices[i++]; 
                    pt.Y = Vertices[i++]; 
                    pt.Z = Vertices[i++]; 

                    Camera.ComputeOffsetOnCameraAxes(data.Transformation * pt, data.m1, data.m2, data.MinQ, data.MaxQ);
                } 
            } 

            protected override void DrawSilhouettes(DrawSilhouettesParams drawSilhouettesParams)
            { 
                //base.DrawSilhouettes(drawSilhouettesParams); 
            } 

            protected override bool InsideOrCrossingFrustum(FrustumParams data)
            { 
                Point3D pt1 = new Point3D(); 
                Point3D pt2 = new Point3D(); 
                Point3D pt3 = new Point3D(); 

                if (data.Transformation == null || data.Transformation.IsIdentity())
                { 
                    for (int i = 0; i < Triangles.Length; /**/)
                    { 
                        GetTriangleVertices(ref i, pt1, pt2, pt3);

                        if (Utility.InsideOrCrossingFrustum(pt1, pt2, pt3, data.Frustum))
                            return true;
                    } 
                } 
                else 
                { 
                    for (int i = 0; i < Triangles.Length; /**/)
                    { 
                        GetTriangleVertices(ref i, pt1, pt2, pt3);

                        if (Utility.InsideOrCrossingFrustum(data.Transformation * pt1, data.Transformation * pt2, data.Transformation * pt3, data.Frustum))
                            return true;
                    } 

                } 

                return false;
            } 

            private void GetTriangleVertices(ref int triangleIndex, Point3D pt1, Point3D pt2, Point3D pt3)
            { 
                int t1; 
                int t2; 
                int t3; 
                t1 = Triangles[triangleIndex++] * 3; 
                t2 = Triangles[triangleIndex++] * 3; 
                t3 = Triangles[triangleIndex++] * 3; 


                pt1.X = Vertices[t1]; 
                pt1.Y = Vertices[t1 + 1]; 
                pt1.Z = Vertices[t1 + 2]; 

                pt2.X = Vertices[t2]; 
                pt2.Y = Vertices[t2 + 1]; 
                pt2.Z = Vertices[t2 + 2]; 

                pt3.X = Vertices[t3]; 
                pt3.Y = Vertices[t3 + 1]; 
                pt3.Z = Vertices[t3 + 2]; 
            } 

            protected override bool InsideOrCrossingScreenPolygon(ScreenPolygonParams data)
            { 
                Point3D pt1 = new Point3D(); 
                Point3D pt2 = new Point3D(); 
                Point3D pt3 = new Point3D(); 

                if (data.Transformation == null || data.Transformation.IsIdentity())
                { 
                    for (int i = 0; i < Triangles.Length; /**/)
                    { 
                        GetTriangleVertices(ref i, pt1, pt2, pt3);

                        if (UtilityEx.InsideOrCrossingScreenPolygon(pt1, pt2, pt3, data))
                            return true;
                    } 
                } 
                else 
                { 
                    for (int i = 0; i < Triangles.Length; /**/)
                    { 
                        GetTriangleVertices(ref i, pt1, pt2, pt3);

                        if (UtilityEx.InsideOrCrossingScreenPolygon(data.Transformation * pt1, data.Transformation * pt2, data.Transformation * pt3, data))
                            return true;
                    } 

                } 

                return false;
            } 

            protected override bool AllVerticesInFrustum(FrustumParams data)
            { 
                Point3D pt = new Point3D(); 

                if (data.Transformation == null || data.Transformation.IsIdentity())
                { 
                    for (int j = 0; j < Vertices.Length; /**/)
                    { 
                        pt.X = Vertices[j++]; 
                        pt.Y = Vertices[j++]; 
                        pt.Z = Vertices[j++]; 

                        if (!Camera.IsInFrustum(pt, data.Frustum))
                        { 
                            return false;
                        } 
                    } 
                } 
                else 
                { 
                    for (int j = 0; j < Vertices.Length; /**/)
                    { 
                        pt.X = Vertices[j++]; 
                        pt.Y = Vertices[j++]; 
                        pt.Z = Vertices[j++]; 

                        if (!Camera.IsInFrustum(data.Transformation * pt, data.Frustum))
                        { 
                            return false;
                        } 
                    } 
                } 
                return true;
            } 

            protected override bool AllVerticesInScreenPolygon(ScreenPolygonParams data)
            { 
                // The screenPolygon is relative to the viewport, so the projected point must not be transalted to the viewport location 
                int[] viewFrame = new int[] { 0, 0, data.ViewportSize.Width, data.ViewportSize.Height };

                Point3D pt = new Point3D(); 

                if (data.Transformation == null || data.Transformation.IsIdentity())
                { 
                    for (int i = 0; i < Vertices.Length; /**/)
                    { 
                        pt.X = Vertices[i++]; 
                        pt.Y = Vertices[i++]; 
                        pt.Z = Vertices[i++]; 

                        if (!UtilityEx.VertexInScreenPolygon(pt, data.ScreenPolygon, data.Min, data.Max, data.ModelViewProj, viewFrame))

                            return false;
                    } 
                } 
                else 
                { 
                    for (int i = 0; i < Vertices.Length; /**/)
                    { 
                        pt.X = Vertices[i++]; 
                        pt.Y = Vertices[i++]; 
                        pt.Z = Vertices[i++]; 

                        pt = data.Transformation * pt; 

                        if (!UtilityEx.VertexInScreenPolygon(pt, data.ScreenPolygon, data.Min, data.Max, data.ModelViewProj, viewFrame))

                            return false;
                    } 

                } 

                return true;
            } 

            protected override bool ThroughTriangle(FrustumParams data)
            { 
                Point3D pt1 = new Point3D(); 
                Point3D pt2 = new Point3D(); 
                Point3D pt3 = new Point3D(); 

                if (data.Transformation == null)
                { 
                    for (int i = 0; i < Triangles.Length; /**/)
                    { 
                        GetTriangleVertices(ref i, pt1, pt2, pt3);

                        if (FrustumEdgesTriangleIntersection(data.SelectionEdges, pt1, pt2, pt3))

                            return true;
                    } 
                } 
                else 
                { 
                    for (int i = 0; i < Triangles.Length; /**/)
                    { 
                        GetTriangleVertices(ref i, pt1, pt2, pt3);

                        if (FrustumEdgesTriangleIntersection(data.SelectionEdges, data.Transformation * pt1, data.Transformation * pt2, data.Transformation * pt3))

                            return true;
                    } 
                } 

                return false;
            } 

            protected override bool ThroughTriangleScreenPolygon(ScreenPolygonParams data)
            { 
                Point3D pt1 = new Point3D(); 
                Point3D pt2 = new Point3D(); 
                Point3D pt3 = new Point3D(); 

                if (data.Transformation == null)
                { 
                    for (int i = 0; i < Triangles.Length; /**/)
                    { 
                        GetTriangleVertices(ref i, pt1, pt2, pt3);

                        if (ThroughTriangleScreenPolygon(pt1, pt2, pt3, data.ScreenPolygon, data.ModelViewProj, data.ViewFrame, null))

                            return true;
                    } 
                } 
                else 
                { 
                    for (int i = 0; i < Triangles.Length; /**/)
                    { 
                        GetTriangleVertices(ref i, pt1, pt2, pt3);

                        if (ThroughTriangleScreenPolygon(data.Transformation * pt1, data.Transformation * pt2, data.Transformation * pt3, data.ScreenPolygon, data.ModelViewProj, data.ViewFrame, null))

                            return true;
                    } 
                } 

                return false;
            } 
        } 

 

Example for using the above class:

            Mesh mesh = Mesh.CreatePlanar(new LinearPath(20, 30), 0.1, meshNatureType.Smooth); 
            mesh.Regen(0.1); 

            // Copy the data of the original mesh to the arrays that will be passed to the VBOMesh object 

            int count = 0; 
            float[] vertices = new float[mesh.Vertices.Length * 3]; 
            foreach (Point3D vertex in mesh.Vertices) 
            { 
                vertices[count++] = (float)vertex.X; 
                vertices[count++] = (float)vertex.Y; 
                vertices[count++] = (float)vertex.Z; 
            } 

            count = 0; 
            float[] normals = new float[mesh.Normals.Length * 3]; 
            foreach (Vector3D n in mesh.Normals) 
            { 
                normals[count++] = (float)n.X; 
                normals[count++] = (float)n.Y; 
                normals[count++] = (float)n.Z; 
            } 

            count = 0; 
            int[] triangles = new int[mesh.Triangles.Length * 3]; 
            foreach (IndexTriangle tri in mesh.Triangles) 
            { 
                triangles[count++] = tri.V1; 
                triangles[count++] = tri.V2; 
                triangles[count++] = tri.V3; 
            } 

            // Create the array of colors 
            Random rand = new Random(12); 
            count = 0; 
            byte[] colors = new byte[mesh.Vertices.Length * 3]; 
            for (int j = 0; j < mesh.Vertices.Length; j++) 
            { 
                colors[count++] = (byte)rand.Next(255); 
                colors[count++] = (byte)rand.Next(255); 
                colors[count++] = (byte)rand.Next(255); 
            } 

            VBOMesh vboMesh = new VBOMesh(); 

            // Set the arrays to the VBOMesh object 
            vboMesh.Vertices = vertices; 
            vboMesh.Normals = normals; 
            vboMesh.Triangles = triangles; 
            vboMesh.Colors = colors; 
            vboMesh.UpdateBoundingBox(null); 
            vboMesh.RegenMode = regenType.CompileOnly; 
            vboMesh.LightWeight = true; 

            viewportLayout1.Entities.Add(vboMesh);

Note:

The mesh must have one normal per vertex (i.e. its MeshNature must be one of the various "Smooth" types) because the vertex attributes for the VBO (normals, colors, texture coordinates...) are associated to the vertices, not the triangles.

If the mesh is one of the "Plain" types, meaning that there is one normal per triangle, the vertices and normals for the triangles (and colors, if it's of ColorPlain type) must be duplicated (and the indices of the triangles vertices updated consequently) so that every triangle vertex has its own normal associated.

Example:

Plain mesh with 4 Vertices, 2 triangles, 2 normals:

Vertices: v1, v2, v3, v4

Triangles: (v1, v2, v3), (v1, v3, v4)

Normals n1, n2

 

To use VBO you must duplicate the vertices common to the 2 triangles and associate a normal to each vertex:

Vertices: v1, v2, v3, v4, v1_1, v3_1

Normals: n1, n1, n1, n2, n2, n2

Triangles: (v1, v2, v3), (v1_1, v3_1, v4)

Have more questions? Submit a request

3 Comments

  • 0
    Avatar
    Daniel

    The function:

    protected override bool ThroughTriangleScreenPolygon(ScreenPolygonParams data)

    Appears to be recursive, and would possibly end up in an infinite loop (I could be reading it wrong). When is this function called?

  • 0
    Avatar
    Luca Cornazzani

    You are right, I just fxed it. That method is used when selecting with the SelectByPolygon mode, if the selection polygon falls inside a triangle without crossing its edges.

  • 0
    Avatar
    Ralf Buchbach

    I would like to test this class because I have a use case where I change an Arrow very often and I'm not able to rotate the viewport or use more than 2 fps. If I do so I receive NullPointerException or ArgumentOutOfRangeException.

    Which libraries do I need to get the OpenGL Code working? I tried some but there were always errors left because functions do not exist or the arguments of the function do not fit

Please sign in to leave a comment.