# Visualization Library A lightweight C++ OpenGL middleware for 2D/3D graphics | [Home] [Tutorials] [All Classes] [Grouped Classes]

# Bicubic Bézier Patches and Surfaces Tutorial

In the following tutorial you will learn how to manage and generate bicubic Bezier surfaces and how to concatenate multiple patches.

A Bézier surface defined by multiple concatenated patches rendered with an ascending (from left to right) tessellation level. The dots and the white lines represent the control points of the patches, the red dots represent the corner control points that are guaranteed to touch the Bézier surface.

From Wikipedia [http://en.wikipedia.org/wiki/Bézier_surface]:

"Bézier surfaces are a species of mathematical spline used in computer graphics, computer-aided design, and finite element modelling. As with the Bézier curve, a Bézier surface is defined by a set of control points. Similar to interpolation in many respects, a key difference is that the surface does not, in general, pass through the central control points; rather, it is "stretched" toward them as though each were an attractive force. They are visually intuitive, and for many applications, mathematically convenient."

"Bézier surfaces were first described in 1972 by the French engineer Pierre Bézier who used them to design automobile bodies. Bézier surfaces can be of any degree, but bicubic Bézier surfaces generally provide enough degrees of freedom for most applications."

As you will se the actual code dealing with the Bezier patches is less than ten lines of code! The majority of the code is dedicated to generate the patch data, to setup the effects, to manage the user input and so on.

[From `App_BezierSurfaces.hpp`]

    class App_BezierSurfaces: public BaseDemo
    {
    public:
      App_BezierSurfaces(): mDetail(10) {}

      void initEvent()
      {
        BaseDemo::initEvent();

        /* 2 pass shader: 1 = solid, 2 = wireframe */

        vl::ref<vl::Effect> fx = new vl::Effect;
        fx->lod(0)->push_back( new vl::Shader);

        fx->shader()->enable(vl::EN_LIGHTING);
        fx->shader()->gocLightModel()->setTwoSide(true);
        fx->shader()->enable(vl::EN_DEPTH_TEST);
        fx->shader()->gocLight(0)->setLinearAttenuation(0.025f);

        fx->shader(0,1)->enable(vl::EN_DEPTH_TEST);
        fx->shader(0,1)->gocPolygonOffset()->set(-0.5f, -0.5f);
        fx->shader(0,1)->enable(vl::EN_POLYGON_OFFSET_LINE);
        fx->shader(0,1)->gocPolygonMode()->set(vl::PM_LINE, vl::PM_LINE);
        fx->shader(0,1)->gocDepthMask()->set(false);

        /* Generate random Bézier patches

        - The simplest bicubic Bézier patch requires 4x4 = 16 control points: A, B, C, D, E, F, G, H, I, L, M, N, O, P, Q, R
        - The Bézier surface is guaranteed to touch only the 4 corner control points A, D, O and R.

        A---B---C---D
        |   |   |   |
        E---F---G---H
        |   |   |   |
        I---L---M---N
        |   |   |   |
        O---P---Q---R

        - You can concatenate two bicubic Bézier patches to form a larger suface by sharing their control points like this:

           patch 1     patch 2
        A---+---+---B---+---+---G
        |   |   |   |   |   |   |
        +---+---+---C---+---+---+
        |   |   |   |   |   |   |
        +---+---+---D---+---+---+
        |   |   |   |   |   |   |
        F---+---+---E---+---+---H

        - In this case the two patches share the control points B, C, D and E.
        - As we can see the total control points needed are 28 = (2 (patches along x) * 3 + 1) * (1 (patches along y) * 3 + 1)
        - Also in this case the Bézier surface is guaranteed to touch only the 6 corner control points A, B, E, F, G and H.

        */

        // This concatenated patch will actually contain 3*2 = 6 bicubic Bézier patches for a total of 70 control points!
        int x = 3;
        int y = 2;
        // We use the formula seen above to compute the number of control points required in each direction.
        vl::ref<vl::BezierPatch> patch1 = new vl::BezierPatch(x*3+1,y*3+1);
        for(int y=0;y<patch1->y();++y)
          for(int x=0;x<patch1->x();++x)
            patch1->points()[x][y] = vl::dvec3(x,vl::randomMinMax(-2.0,+2.0),y);

        /* Add Bézier patches to our Bézier surface */

        // Instance our Bézier surface
        mBezier = new vl::BezierSurface;
        // We can actually add multiple Bézier patches
        mBezier->patches().push_back(patch1.get());
        // Define the subdivision detail
        mBezier->setDetail(mDetail);
        // Generate the actual geometry using the current patches and detail
        mBezier->updateBezierSurface(false);
        // Compute the normals as we have lighting activated
        mBezier->computeNormals();
        // Used by the line rendering
        mBezier->setColorArray(vlut::blue);
        // Add the Bézier surface to our scene
        sceneManager()->tree()->addActor(mBezier.get(), fx.get(), NULL);

        /* Show the control points */

        showPatchControlPoints(mBezier.get());
      }

      /*
        up/down arrow = increase/decrease the Bézier surface tessellation detail
        space         = toggle control points visibility
      */
      void keyPressEvent(unsigned short ch, vl::EKey key)
      {
        BaseDemo::keyPressEvent(ch,key);
        if (key == vl::Key_Up)
        {
          ++mDetail;
          mDetail = vl::clamp(mDetail, 2, 64);
          mBezier->setDetail(mDetail);
          mBezier->updateBezierSurface(false);
          mBezier->computeNormals();
          mBezier->setVBODirty(true);
        }
        else
        if (key == vl::Key_Down)
        {
          --mDetail;
          mDetail = vl::clamp(mDetail, 2, 64);
          mBezier->setDetail(mDetail);
          mBezier->updateBezierSurface(false);
          mBezier->computeNormals();
          mBezier->setVBODirty(true);
        }
        else
        if(key == vl::Key_Space)
        {
          if (sceneManager()->tree()->actors()->find(mCtrlPoints_Actor.get()) == -1)
            sceneManager()->tree()->actors()->push_back(mCtrlPoints_Actor.get());
          else
            sceneManager()->tree()->actors()->erase(mCtrlPoints_Actor.get());
        }
      }

      /* Generates the geometry to render the control points */
      void showPatchControlPoints(vl::BezierSurface* bezier)
      {
        vl::ref<vl::Effect> fx = new vl::Effect;
        fx->shader()->enable(vl::EN_DEPTH_TEST);
        fx->shader()->gocPolygonMode()->set(vl::PM_LINE, vl::PM_LINE);
        fx->shader()->gocLineWidth()->set(1.0f);
        fx->shader()->gocPointSize()->set(5.0f);

        vl::ref<vl::Geometry> geom = new vl::Geometry;

        int istart = 0;
        std::vector<vl::fvec3> verts;
        std::vector<vl::fvec4> colos;
        for(unsigned ipatch=0; ipatch<bezier->patches().size(); ++ipatch)
        {
          const vl::BezierPatch::Points& p = bezier->patches()[ipatch]->points();
          for(unsigned ix=0; ix<p.size()-3;     ix+=3)
          for(unsigned iy=0; iy<p[ix].size()-3; iy+=3, istart+=16)
          {
            verts.push_back((vl::fvec3)p[ix+0][iy+0]); colos.push_back(vlut::red);
            verts.push_back((vl::fvec3)p[ix+0][iy+1]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+0][iy+2]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+0][iy+3]); colos.push_back(vlut::red);

            verts.push_back((vl::fvec3)p[ix+1][iy+0]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+1][iy+1]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+1][iy+2]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+1][iy+3]); colos.push_back(vlut::white);

            verts.push_back((vl::fvec3)p[ix+2][iy+0]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+2][iy+1]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+2][iy+2]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+2][iy+3]); colos.push_back(vlut::white);

            verts.push_back((vl::fvec3)p[ix+3][iy+0]); colos.push_back(vlut::red);
            verts.push_back((vl::fvec3)p[ix+3][iy+1]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+3][iy+2]); colos.push_back(vlut::white);
            verts.push_back((vl::fvec3)p[ix+3][iy+3]); colos.push_back(vlut::red);

            vl::ref<vl::DrawArrays> da = new vl::DrawArrays(vl::PT_POINTS,istart,16);
            vl::ref<vl::DrawElementsUInt> de = new vl::DrawElementsUInt(vl::PT_QUADS);
            de->indices()->resize(4*9);
            unsigned int quads[] = { 0,1,5,4, 4,5,9,8, 8,9,13,12, 1,2,6,5, 5,6,10,9, 9,10,14,13, 2,3,7,6, 6,7,11,10, 10,11,15,14 };
            for(int q=0; q<4*9; ++q)
              quads[q] += istart;
            memcpy(de->indices()->ptr(), quads, sizeof(quads));
            geom->primitives()->push_back(de.get());
            geom->primitives()->push_back(da.get());
          }
        }

        vl::ref<vl::ArrayFVec3> vert_array = new vl::ArrayFVec3;
        geom->setVertexArray(vert_array.get());
        *vert_array = verts;

        vl::ref<vl::ArrayFVec4> cols_array = new vl::ArrayFVec4;
        geom->setColorArray(cols_array.get());
        *cols_array = colos;

        mCtrlPoints_Actor = sceneManager()->tree()->addActor(geom.get(), fx.get(), NULL);
      }

    protected:
      vl::ref<vl::BezierSurface> mBezier;
      vl::ref<vl::Actor> mCtrlPoints_Actor;
      int mDetail;
    };

    // Have fun!

Visualization Library v2009.08 Reference Documentation
Copyright 2005-2009 Michele Bosi. All rights reserved.
Updated on Sun Nov 8 14:28:52 2009.
Permission is granted to use this page to write and publish articles regarding Visualization Library.