Visualization Library v1.0.3

A lightweight C++ OpenGL middleware for 2D/3D graphics

VL     Star     Watch     Fork     Issue

[Download] [Tutorials] [All Classes] [Grouped Classes]

Volume Rendering and Isosurfaces Extraction with Marching Cubes Tutorial

This tutorial demonstrates how to use the marching cubes algorithm to extract isosurfaces from volume data.

pagGuideMarchingCubes.jpg
pagGuideMarchingCubes_5.jpg
pagGuideMarchingCubes_1.jpg
pagGuideMarchingCubes_2.jpg
pagGuideMarchingCubes_3.jpg
pagGuideMarchingCubes_4.jpg

[From App_MarchingCubes.cpp]

class App_MarchingCubes: public BaseDemo
//
// This applet demonstrates: how to use marching cubes to generate isosurfaces, how to visualize multiple intersecting volumes, 
// how to manage transparencies, how to color the isosurface using texturing and vertex color, how to implement a simple metaballs demo
// and how to simulate a metaballs/water fountain.
//
// Test 0: drag and drop a volume in the window and use the mouse wheel to visualize the appropriate iso-surface level.
// Test 1: multiple self-intersecting transparent isosurfaces visualization.
// Test 2: isosurface colorize via texturing.
// Test 3: isosurface colorize via vertex color.
// Test 4: animated metaball demo implemented on top of the marching cube algorithm.
// Test 5: animated fountain based on marching cubes.
// Test 6: 3D function plotting with vl::VolumePlot
//
{
public:
  App_MarchingCubes(): mTest(4) {}

  void initEvent()
  {
    vl::Log::notify(appletInfo());
    openglContext()->setContinuousUpdate(true);

    srand((unsigned int)time(NULL));

    mThreshold = 0.44f;
    mFountainSpeed = 2.5f;

    // setup metaballs data

    mMetaball.resize(mParticleCount);
    mMetaballVelocity.resize(mParticleCount);
    mMetaballsFrames.resize(mFrameCount);
    for(int iframe=0; iframe<mFrameCount; ++iframe)
    {
      mMetaballsFrames[iframe].resize(mParticleCount);
      for(int iball=0; iball<mParticleCount; ++iball)
      {
        mMetaballsFrames[iframe][iball].x() = (rand()%100 / 100.0f)*0.6f+0.2f;
        mMetaballsFrames[iframe][iball].y() = (rand()%100 / 100.0f)*0.6f+0.2f;
        mMetaballsFrames[iframe][iball].z() = (rand()%100 / 100.0f)*0.6f+0.2f;
      }
    }

    // load images

    mVolumeImage = vl::loadImage("/volume/VLTest.dat")->convertFormat(vl::IF_LUMINANCE)->convertType(vl::IT_FLOAT);
    mColorImage  = vl::loadImage("/volume/VLTest.dat");

    // transform

    mTransform = new vl::Transform;
    rendering()->as<vl::Rendering>()->transform()->addChild( mTransform.get() );

    // text setup

    mText = new vl::Text();
    mText->setDisplayListEnabled(!vl::Has_GLES);
    mText->setFont( vl::defFontManager()->acquireFont("/font/bitstream-vera/VeraMono.ttf", 10) );
    mText->setMargin(5);
    mText->setViewportAlignment(vl::AlignTop | vl::AlignHCenter);
    mText->setAlignment(vl::AlignTop | vl::AlignHCenter);
    mText->setTextAlignment(vl::TextAlignCenter);
    mText->setColor(vl::white);
    mText->setBackgroundColor(vl::fvec4(0,0,0,.75f));
    mText->setBackgroundEnabled(true);
    mText->setText("Marching Cubes Demo");

    vl::ref< vl::Effect > text_fx = new vl::Effect;
    text_fx->shader()->enable(vl::EN_BLEND);

    mTextActor = new vl::Actor( mText.get(), text_fx.get() );
    sceneManager()->tree()->addActor( mTextActor.get() );

    setupTest();
  }

  virtual void updateScene()
  {
    if (mTest == 4)
      runTest4();
    else
    if (mTest == 5)
      runTest5();
  }

  void runTest4()
  {
    // rotate metaballs
    vl::vec3 axis;
    axis.x() = sin( vl::Time::currentTime()*3.1415f*2.0f/8.0f );
    axis.y() = 0;
    axis.z() = cos( vl::Time::currentTime()*3.1415f*2.0f/8.0f );
    axis.normalize();
    if (axis.isNull())
      return;
    mTransform->setLocalMatrix( vl::mat4::getRotation( vl::Time::currentTime()*120.0f, axis) );
    // animate metaballs
    float t = (float)vl::fract( vl::Time::currentTime()*0.05f );
    t = vl::clamp(t, 0.0f, 0.999f);
    int frame1 = int(t * mFrameCount);
    int frame2 = (frame1+1) % mFrameCount;
    t = vl::fract(t*mFrameCount);
    float Ha = 2*t*t*t - 3*t*t + 1;
    float Hb = -2*t*t*t + 3*t*t;
    for(unsigned iball=0; iball<mMetaball.size(); ++iball)
      mMetaball[iball] = mMetaballsFrames[frame1][iball]*Ha + mMetaballsFrames[frame2][iball]*Hb;

    int idx = 0;
    float* values = &mMarchingCubes.volumeInfo()->at(0)->volume()->value(0);
    for(int z=0; z<mMetaballsResolution; ++z)
    {
      float pz = (float)z/mMetaballsResolution;
      for(int y=0; y<mMetaballsResolution; ++y)
      {
        float py = (float)y/mMetaballsResolution;
        for(int x=0; x<mMetaballsResolution; ++x, ++idx)
        {
          values[idx] = metaballFunction((float)x/mMetaballsResolution, py, pz);
        }
      }
    }
    // notify that the volume data has changed
    mMarchingCubes.volumeInfo()->at(0)->volume()->setDataDirty();
    mMarchingCubes.run(false);
  }

  void runTest5()
  {
    // animate metaballs
    for(unsigned iball=0; iball<mMetaball.size(); ++iball)
    {
      mMetaballVelocity[iball] -= vl::fvec3(0,1.5f,0)*(float)mTimer.elapsed()*mFountainSpeed;

      mMetaball[iball] = mMetaball[iball] + mMetaballVelocity[iball]*(float)mTimer.elapsed();
      if (mMetaball[iball].y() < 0)
      {
        mMetaball[iball].y() = 0;
        mMetaball[iball].x() = 0.5f;
        mMetaball[iball].z() = 0.5f;
        mMetaballVelocity[iball].x() = (float)vl::random(-12,+12);
        mMetaballVelocity[iball].z() = (float)vl::random(-12,+12);
        mMetaballVelocity[iball].y() = 100;
        mMetaballVelocity[iball].normalize();
        mMetaballVelocity[iball] *= (17.0f + (float)vl::random(0,5))*0.05f*mFountainSpeed;
      }
    }
    mTimer.start();

    int idx = 0;
    float* values = &mMarchingCubes.volumeInfo()->at(0)->volume()->value(0);
    for(int z=0; z<mMetaballsResolution; ++z)
    {
      float pz = (float)z/mMetaballsResolution;
      for(int y=0; y<mMetaballsResolution; ++y)
      {
        float py = (float)y/mMetaballsResolution;
        for(int x=0; x<mMetaballsResolution; ++x, ++idx)
          values[idx] = metaballFunction((float)x/mMetaballsResolution, py, pz);
      }
    }
    // notify that the volume data has changed
    mMarchingCubes.volumeInfo()->at(0)->volume()->setDataDirty();
    mMarchingCubes.run(false);
  }

  float metaballFunction(float x, float y, float z)
  {
    float val = 0;
    for(unsigned i=0; i<mMetaball.size(); ++i)
    {
      float rx = x-mMetaball[i].x();
      float ry = y-mMetaball[i].y();
      float rz = z-mMetaball[i].z();
      float r2 = rx*rx+ry*ry+rz*rz;
      if (r2 == 0.0f)
        return 1.0e+10f;
      val += vl::fast1_inversesqrt(r2*r2);    // threshold = 400
      // val += vl::fast2_inversesqrt(r2*r2); // threshold = 400
      // val += 1.0f / r2;                    // threshold = 400
      // val += vl::fast1_inversesqrt(r2);    // threshold = 70
      // val += vl::fast2_inversesqrt(r2);    // threshold = 70
    }
    return val;
  }

  void keyPressEvent(unsigned short ch, vl::EKey key)
  {
    BaseDemo::keyPressEvent(ch,key);
    bool update = false;
    if (key == vl::Key_Right)
    {
      mTest++;
      update = true;
    }
    else
    if (key == vl::Key_Left)
    {
      mTest--;
      update = true;
    }
    if (update)
    {
      if (mTest > 6) mTest = 0;
      if (mTest < 0) mTest = 6;
      setupTest();
      updateText();
    }
  }

  // generates the 2 transparent volumes or a single volume
  vl::Actor* showVolumes(bool test1)
  {
    rendering()->as<vl::Rendering>()->camera()->setModelingMatrix( vl::mat4::getTranslation(0,0,20) );

    vl::ref<vl::Geometry > geom = new vl::Geometry;
    geom->setVertexArray(mMarchingCubes.mVertsArray.get());
    geom->setNormalArray(mMarchingCubes.mNormsArray.get());
    geom->drawCalls()->push_back(mMarchingCubes.mDrawElements.get());

    vl::ref<vl::Effect> fx = new vl::Effect;
    fx->shader()->setRenderState( new vl::Light, 0 );
    fx->shader()->enable(vl::EN_LIGHTING);
    fx->shader()->enable(vl::EN_DEPTH_TEST);
    // two side lighting
    fx->shader()->gocLightModel()->setTwoSide(true);

    vl::ref<vl::Actor> act = new vl::Actor(geom.get(), fx.get(), NULL);
    sceneManager()->tree()->actors()->clear();
    sceneManager()->tree()->addActor(act.get());
    sceneManager()->tree()->addActor( mTextActor.get() );

    vl::ref<vl::Volume> volume = new vl::Volume;
    volume->setup( (float*)mVolumeImage->pixels(), false, true, vl::fvec3(-5,-5,-5), vl::fvec3(+5,+5,+5), vl::ivec3(mVolumeImage->width(), mVolumeImage->height(), mVolumeImage->depth()) );
    
    mMarchingCubes.reset();
    mMarchingCubes.volumeInfo()->push_back( new vl::VolumeInfo( volume.get(), 0.40f, /*yellow*/vl::fvec4(1, 1, 0, 0.5f)) );
    if (test1)
    {
      // generate second volume
      mMarchingCubes.volumeInfo()->push_back( new vl::VolumeInfo( volume.get(), 0.50f, /*red*/vl::fvec4(1, 0, 0, 0.5f)) );
      // enable blend
      fx->shader()->enable(vl::EN_BLEND);
      // use color array
      fx->shader()->gocMaterial()->setColorMaterialEnabled(true);
      // polygon depth sorting
      act->actorEventCallbacks()->push_back( new vl::DepthSortCallback );
      // binds the marching cubes color array
      geom->setColorArray (mMarchingCubes.mColorArray.get());
    }

    mMarchingCubes.run(true);

    // stats
    vl::String stats;
    stats += vl::Say("vertices = %n\n") << mMarchingCubes.mVertsArray->size();
    vl::Log::print(stats);

    return act.get();
  }

  // Coloring a volume using a 3d texture can be very useful when the color of the texture is animated
  // i.e. changes relatively rapidly or when the isosurface itself changes or is animated.
  // Cons:
  // 1 - the texture can occupy a large amount of memory
  // 2 - to guarantee maximum compatibility the texture should be a cube (not a rectangle) whose side length is a power of 2.
  void textureVolumeColor(vl::Actor* vol_act)
  {
    if (!vl::Has_Texture_3D)
    {
      vl::Log::notify("textureVolumeColor() requires 3D texturing.\n");
      return;
    }

    vl::Effect* fx = vol_act->effect();

    // automatic 3D texture coordinate generation
    #if 1
      fx->shader()->gocTextureSampler(0)->setTexture( new vl::Texture( mColorImage.get() ) );
      fx->shader()->gocTexGen(0)->setGenModeS(vl::TGM_OBJECT_LINEAR);
      fx->shader()->gocTexGen(0)->setGenModeT(vl::TGM_OBJECT_LINEAR);
      fx->shader()->gocTexGen(0)->setGenModeR(vl::TGM_OBJECT_LINEAR);
      /* the settings below are the defaults
      fx->shader()->gocTexGen(0)->setObjectPlaneS(vl::fvec4(1,0,0,0));
      fx->shader()->gocTexGen(0)->setObjectPlaneT(vl::fvec4(0,1,0,0));
      fx->shader()->gocTexGen(0)->setObjectPlaneR(vl::fvec4(0,0,1,0));
      fx->shader()->gocTexEnv(0)->setMode(vl::TEM_MODULATE);*/
      vol_act->lod(0)->computeBounds();
      vl::AABB box = vol_act->lod(0)->boundingBox();
      // transforms object vertex coordinates to 0..1 cube, this might create a "bleeding color" effect at the very side of the cube.
      // note that to be really precise we should use 1/2N ... 1-1/2N in each direction instead of 0..1.
      vl::mat4 tex_mat = vl::mat4::getTranslation( vl::vec3(0.5f,0.5f,0.5f) ) * 
                         vl::mat4::getScaling(1.0f/box.width(),1.0f/box.height(),1.0f/box.depth()) * 
                         vl::mat4::getTranslation( -box.center() );
      fx->shader()->gocTextureMatrix(0)->setMatrix((vl::fmat4)tex_mat);
    #endif

    // the above code is equivalent to the code below that manually generates the texture coordinates.
    #if 0
      // transforms object vertex coordinates to 0..1 cube.
      vol_act->lod(0)->computeBounds();
      vl::AABB box = vol_act->lod(0)->boundingBox();
      vl::mat4 tex_mat = vl::mat4::getTranslation( vl::vec3(0.5f,0.5f,0.5f) ) * 
                         vl::mat4::getScaling(1.0f/box.width(),1.0f/box.height(),1.0f/box.depth()) * 
                         vl::mat4::getTranslation( -box.center() );

      fx->shader()->gocTextureSampler(0)->setTexture( new vl::Texture( mColorImage.get() ) );
      vl::ref<vl::ArrayFloat3> tex_array = new vl::ArrayFloat3;
      tex_array->resize( geom->vertexArray()->size() );
      geom->setTexCoordArray(0, tex_array.get());
      for(int i=0; i<tex_array->size(); ++i)
        tex_array->at(i) = ((vl::fvec4)(tex_mat * geom->vertexArray()->getAsVec4(i))).xyz();
    #endif
  }

  // Coloring a volume assigning a color to each vertex directly can save memory compared to using a 3D texture, 
  // and no 3D texturing is needed so there aren't compatibility issues. 
  // This technique is not particularly efficient if your isosurfaces are animated and you need to recompute the colors often.
  void vertexVolumeColor(vl::Actor* vol_act)
  {
    vl::Geometry* geom = vl::cast<vl::Geometry>(vol_act->lod(0));
    vl::Effect* fx = vol_act->effect();
    // use color array
    fx->shader()->gocMaterial()->setColorMaterialEnabled(true);

    // transforms object vertex coordinates to 0..1 cube.
    vol_act->lod(0)->computeBounds();
    vl::AABB box = vol_act->lod(0)->boundingBox();
    vl::mat4 tex_mat = vl::mat4::getTranslation( vl::vec3(0.5f,0.5f,0.5f) ) * 
                       vl::mat4::getScaling(1.0f/box.width(),1.0f/box.height(),1.0f/box.depth()) * 
                       vl::mat4::getTranslation( -box.center() );

    vl::ref<vl::ArrayFloat4> color_array = new vl::ArrayFloat4;
    color_array->resize( geom->vertexArray()->size() );
    geom->setColorArray(color_array.get());
    for(size_t i=0; i<color_array->size(); ++i)
    {
      vl::vec4 px = tex_mat * geom->vertexArray()->getAsVec4(i);
      px.x() *= mColorImage->width();
      px.y() *= mColorImage->height();
      px.z() *= mColorImage->depth();
      color_array->at(i) = mColorImage->sampleLinear(px.x(), px.y(), px.z());
    }
  }

  // Setups the good old metaballs demo!
  vl::Actor* setupMetaballs()
  {
    rendering()->as<vl::Rendering>()->camera()->setModelingMatrix( vl::mat4::getTranslation(0,0,25) );

    sceneManager()->tree()->actors()->clear();
    sceneManager()->tree()->addActor( mTextActor.get() );

    vl::ref<vl::Volume> volume = new vl::Volume;
    mMarchingCubes.reset();
    mMarchingCubes.volumeInfo()->push_back( new vl::VolumeInfo( volume.get(), 400.0f) );
    mMarchingCubes.volumeInfo()->at(0)->volume()->setup( NULL, false, false, vl::fvec3(-10,-10,-10), vl::fvec3(+10,+10,+10), vl::ivec3(mMetaballsResolution,mMetaballsResolution,mMetaballsResolution) );
    
    vl::ref<vl::Geometry > geom = new vl::Geometry;
    geom->setVertexArray(mMarchingCubes.mVertsArray.get());
    geom->setNormalArray(mMarchingCubes.mNormsArray.get());
    geom->drawCalls()->push_back(mMarchingCubes.mDrawElements.get());
    // disable BufferObject since we update the vertices every frame
    geom->setBufferObjectEnabled(false);

    vl::ref<vl::Effect> fx = new vl::Effect;
    fx->shader()->setRenderState( new vl::Light, 0 );
    fx->shader()->enable(vl::EN_LIGHTING);
    fx->shader()->enable(vl::EN_DEPTH_TEST);
    // two side lighting
    fx->shader()->gocLightModel()->setTwoSide(true);

    if (vl::Has_GL_Version_1_1)
    {
      vl::ref<vl::Image> texture = vl::loadImage("/images/spheremap.png");
      fx->shader()->gocTextureSampler(0)->setTexture( new vl::Texture( texture.get() ) );
      fx->shader()->gocTexGen(0)->setGenModeS(vl::TGM_SPHERE_MAP);
      fx->shader()->gocTexGen(0)->setGenModeT(vl::TGM_SPHERE_MAP);
      fx->shader()->gocTexEnv(0)->setMode(vl::TEM_BLEND);
    }

    vl::ref<vl::Actor> act = sceneManager()->tree()->addActor(geom.get(), fx.get(), mTransform.get());

    return act.get();
  }

  // Setups the metaballs fountain demo!
  vl::Actor* setupFountain()
  {
    rendering()->as<vl::Rendering>()->camera()->setModelingMatrix( vl::mat4::getTranslation(0,0,25) );

    sceneManager()->tree()->actors()->clear();
    sceneManager()->tree()->addActor( mTextActor.get() );

    vl::ref<vl::Volume> volume = new vl::Volume;
    mMarchingCubes.reset();
    mMarchingCubes.volumeInfo()->push_back( new vl::VolumeInfo( volume.get(), 400.0f) );
    mMarchingCubes.volumeInfo()->at(0)->volume()->setup( NULL, false, false, vl::fvec3(-10,-10,-10), vl::fvec3(+10,+10,+10), vl::ivec3(mMetaballsResolution,mMetaballsResolution,mMetaballsResolution) );
    
    vl::ref<vl::Geometry > geom = new vl::Geometry;
    geom->setBoundingBox( vl::AABB( vl::vec3(-10,-10,1-10), vl::vec3(10,10,10) ) );
    geom->setBoundingSphere( geom->boundingBox() );
    geom->setVertexArray(mMarchingCubes.mVertsArray.get());
    geom->setNormalArray(mMarchingCubes.mNormsArray.get());
    geom->drawCalls()->push_back(mMarchingCubes.mDrawElements.get());
    // disable BufferObject since we update the vertices every frame
    geom->setBufferObjectEnabled(false);

    vl::ref<vl::Effect> fx = new vl::Effect;
    fx->shader()->setRenderState( new vl::Light, 0 );
    fx->shader()->enable(vl::EN_LIGHTING);
    fx->shader()->enable(vl::EN_DEPTH_TEST);
    // two side lighting
    fx->shader()->gocLightModel()->setTwoSide(true);
    fx->shader()->gocMaterial()->setDiffuse(vl::royalblue);

    vl::ref<vl::Actor> act = sceneManager()->tree()->addActor(geom.get(), fx.get(), mTransform.get());

    for(unsigned iball=0; iball<mMetaball.size(); ++iball)
    {
      mMetaball[iball].y() = 0;
      mMetaball[iball].x() = 0.5f;
      mMetaball[iball].z() = 0.5f;
      mMetaballVelocity[iball].x() = (float)vl::random(-15,+15);
      mMetaballVelocity[iball].z() = (float)vl::random(-15,+15);
      mMetaballVelocity[iball].y() = 100;
      mMetaballVelocity[iball].normalize();
      mMetaballVelocity[iball] *= (5.0f + (float)vl::random(0,20))*0.05f*mFountainSpeed;
    }

    mTransform->setLocalMatrix(vl::mat4());

    mTimer.start();
    return act.get();
  }

  class my_func: public vl::VolumePlot::Function
  {
  public:
    virtual float operator()(float x, float y, float z) const
    {
      // return sqrt(x*x+y*y+z*z); == 0.9f
      // return exp(-y)*sin(z)+cos(z*x); // == 2.0f
      return -x/5.0f*sin(z/5.0f)+exp(y*y*y/5.0f/5.0f/5.0f); // == 0.900f
    }
  };

  // Shows how to use vl::VolumePlot to create a 3D plot.
  void setup3Dplot()
  {
    // reset actors and camera
    sceneManager()->tree()->actors()->clear();
    sceneManager()->tree()->addActor( mTextActor.get() );
    rendering()->as<vl::Rendering>()->camera()->setViewMatrix( vl::mat4::getLookAt( vl::vec3(5,10,20), vl::vec3(0,0,0), vl::vec3(0,1,0)) );

    float range = 5.0f;
    vl::fvec3 min_corner(-range,-range,-range);
    vl::fvec3 max_corner(+range,+range,+range);

    vl::VolumePlot plot;
    plot.setMinCorner(min_corner);
    plot.setMaxCorner(max_corner);
    plot.compute( my_func(), 0.900f );
    sceneManager()->tree()->addChild(plot.actorTreeMulti());
  }

  void loadVolume(vl::ref<vl::Image> vol_img)
  {
    // reset camera
    rendering()->as<vl::Rendering>()->camera()->setModelingMatrix( vl::mat4::getTranslation(0,0,20) );

    // reset actors
    sceneManager()->tree()->actors()->clear();
    sceneManager()->tree()->addActor( mTextActor.get() );

    // convert the image to a one-component float volume
    // and keep it alive since we use its data directly
    mDropImage = vol_img->convertFormat(vl::IF_LUMINANCE)->convertType(vl::IT_FLOAT);

    // note: we use the image data directly without making copies:
    vl::ref<vl::Volume> volume = new vl::Volume;
    volume->setup( (float*)mDropImage->pixels(), true, false, vl::fvec3(-5,-5,-5), vl::fvec3(+5,+5,+5), vl::ivec3(mDropImage->width(), mDropImage->height(), mDropImage->depth()) );

    // start timing
    vl::Time time; 
    
    #if 0
      // downsample volume data and perform timing.
      time.start();
      volume = volume->downsample();
      vl::Log::print( vl::Say("Downsampling time = %.2n\n") << time.elapsed() );
    #endif

    mMarchingCubes.reset();
    mMarchingCubes.volumeInfo()->push_back( new vl::VolumeInfo(volume.get(), mThreshold) );

    // run MarchingCubes with timing.
    time.start();
    mMarchingCubes.run(false);
    vl::Log::print( vl::Say("Marching cubes: time = %.2n, verts = %n\n") << time.elapsed() << mMarchingCubes.mVertsArray->size() );

    // setup isosurface geometry, actor and effect

    // geometry
    mIsosurfGeom = new vl::Geometry;
    // install vertex and normal arrays and primitives generated by the marching cube algorithm
    mIsosurfGeom->setVertexArray(mMarchingCubes.mVertsArray.get());
    mIsosurfGeom->setNormalArray(mMarchingCubes.mNormsArray.get());
    mIsosurfGeom->drawCalls()->push_back(mMarchingCubes.mDrawElements.get());

    #if 0
      time.start();
      vl::PolygonSimplifier ps;
      ps.simplify( 0.1f, mIsosurfGeom.get() );
      mIsosurfGeom->computeNormals();
      vl::Log::print( vl::Say("Simplification: time = %.2n\n") << time.elapsed() );
    #endif

    vl::ref<vl::Effect> fx = new vl::Effect;

    // effect
    fx->shader()->setRenderState( new vl::Light, 0 );
    fx->shader()->enable(vl::EN_DEPTH_TEST);
    fx->shader()->enable(vl::EN_LIGHTING);
    fx->shader()->gocLightModel()->setTwoSide(true);
    fx->shader()->gocMaterial()->setBackDiffuse(vl::green);

    // show the volume in wireframe to see the tessellation.
#if defined(VL_OPENGL)
    fx->lod(0)->push_back( new vl::Shader );
    fx->shader(0,1)->enable(vl::EN_CULL_FACE);
    fx->shader(0,1)->enable(vl::EN_DEPTH_TEST);
    fx->shader(0,1)->enable(vl::EN_POLYGON_OFFSET_LINE);
    fx->shader(0,1)->gocPolygonOffset()->set(-1.0f, -1.0f);
    fx->shader(0,1)->gocPolygonMode()->set(vl::PM_LINE, vl::PM_LINE);
    fx->shader(0,1)->gocColor()->setValue(vl::royalblue);
#endif

    // actor
    vl::ref<vl::Actor> actor = new vl::Actor(mIsosurfGeom.get(), fx.get());

    // add actor to the scene
    sceneManager()->tree()->addActor( actor.get() );

    updateText();
  }

  void setupTest()
  {
    sceneManager()->tree()->eraseAllChildren();
    if (mTest == 1)
      showVolumes(true);
    else
    if (mTest == 2)
      textureVolumeColor( showVolumes(false) );
    else
    if (mTest == 3)
      vertexVolumeColor( showVolumes(false) );
    else
    if (mTest == 4)
      setupMetaballs();
    else
    if (mTest == 5)
      setupFountain();
    else
    if (mTest == 6)
      setup3Dplot();

    updateText();
  }

  void updateText()
  {
    vl::String str;
    if(mTest == 0)
      str = vl::Say("Marching Cubes Test #0 - Volume Viewer - threshold = %.2n\n(drop a .dat file)") << mThreshold;
    else
    if(mTest == 1)
      str = "Marching Cubes Test #1 - Multiple Volumes & Transparency";
    else
    if(mTest == 2)
      str = "Marching Cubes Test #2 - Texture Colorized Volumes";
    else
    if(mTest == 3)
      str = "Marching Cubes Test #3 - Vertex Colorized Volumes";
    else
    if(mTest == 4)
      str = "Marching Cubes Test #4 - Metaballs";
    else
    if(mTest == 5)
      str = "Marching Cubes Test #5 - Fountain";
    else
    if(mTest == 6)
      str = "Marching Cubes Test #6 - 3D Function Plotting";

    str += "\n(press the <- or -> key to change test)";
    mText->setText( str );
    mText->setDisplayListDirty(true);
  }

  void mouseWheelEvent(int w)
  {
    if (mTest != 0)
      return;

    if (w>0)
      mThreshold += 0.010f;
    else
      mThreshold -= 0.010f;

    mThreshold = vl::clamp(mThreshold, 0.0f, 1.0f);

    vl::Time time; time.start();
    mMarchingCubes.volumeInfo()->at(0)->setThreshold(mThreshold);
    mMarchingCubes.run(false);
    if (mIsosurfGeom)
      mIsosurfGeom->setBufferObjectDirty(true);

    vl::Log::print( vl::Say("Marching cubes: time = %.2n, verts = %n\n") << time.elapsed() << mMarchingCubes.mVertsArray->size() );

    updateText();

    openglContext()->update();
  }

  void fileDroppedEvent(const std::vector<vl::String>& files)
  {
    mTest = 0;

    if(files.size() == 1)
    {
      if (files[0].endsWith(".dat"))
      {
        vl::ref<vl::Image> vol_img = vl::loadImage(files[0]);
        loadVolume(vol_img);
      }
    }
    else
    {
      std::vector<vl::String> files_sorted = files;
      std::sort(files_sorted.begin(), files_sorted.end());
      std::vector< vl::ref<vl::Image> > images;
      for(unsigned int i=0; i<files_sorted.size(); ++i)
        images.push_back(vl::loadImage(files_sorted[i])->convertFormat(vl::IF_LUMINANCE)->convertType(vl::IT_UNSIGNED_BYTE));
      vl::ref<vl::Image> vol_img = vl::assemble3DImage(images);
      if (vol_img)
        loadVolume(vol_img);
    }
  }

  void resizeEvent(int w, int h)
  {
    BaseDemo::resizeEvent(w,h);
    mText->setDisplayListDirty(true);
  }

protected:
  vl::ref<vl::ResourceDatabase> mResDB;
  vl::ref<vl::Transform> mTransform;
  vl::ref< vl::Text > mText;
  vl::ref< vl::Actor > mTextActor;

  // volume
  vl::MarchingCubes mMarchingCubes;
  float mThreshold;
  std::vector<vl::fvec3> mMetaball;
  std::vector<vl::fvec3> mMetaballVelocity;
  vl::Time mTimer;
  std::vector< std::vector<vl::fvec3> > mMetaballsFrames;
  static const int mMetaballsResolution = 32;
  static const int mParticleCount = 25;
  static const int mFrameCount    = 20;
  float mFountainSpeed;
  vl::ref<vl::Image> mVolumeImage;
  vl::ref<vl::Image> mColorImage;
  vl::ref<vl::Image> mDropImage;
  vl::ref<vl::Geometry> mIsosurfGeom;
  int mTest;
};

// Have fun!


Visualization Library v1.0.3 Reference Documentation
Copyright Michele Bosi. All rights reserved.
Updated on Tue Feb 7 2017 00:55:05.
Permission is granted to use this page to write and publish articles regarding Visualization Library.