Marching Cubes 算法再探

news2024/11/15 17:43:57

Marching Cubes 算法再探

  • 基础
  • 过程
  • 代码
    • mian.cpp
    • MarchingCubes.h
    • MarchingCubes.cpp

之前做NeRF相关工作时简单看过,但没有深究其实现,知其然不知其所以然的程度,算是初探。

基础

为了对MC有大致了解,可以先根据Marching Cubes 算法初探,理解一下Marching Cubes这个名字的由来,其二维空间中的示例可谓一目了然。

接下来结合Efficient Implementation of Marching Cubes’ Cases with Topological Guarantees
的论文和代码,来探究一下CPU版本经典MC的具体实现过程。

过程

整个过程的核心部分可以抽象为以下步骤:

  1. 对每一个cube,比较其顶点与等值面的大小,得到顶点的8bit案例编码

顶点1、5、7值为正,则得到10100010,即162

在这里插入图片描述

  1. 根据顶点案例的8bit编码在查找表中找到相应三角形数组

162对应的三角形数组为{5,0,1,5,4,0,7,6,11}

!

  1. 根据三角形数组添加\绘制三角形

这个三角形数组每三个值代表一个三角形的三个顶点位置(先以cube边索引形式给出)

在这里插入图片描述

添加三角形[5,0,1]

在这里插入图片描述

添加三角形[5,4,0]

在这里插入图片描述

添加三角形[7,6,11]

在这里插入图片描述

  1. 遍历所有cube,得到所有三角形的并集

在这里插入图片描述

在这里插入图片描述

以上图截取于视频,这个视频也很有意思。


代码

接下来看下大佬Thomas Lewiner(Efficient Implementation of Marching Cubes’ Cases with Topological Guarantees一作)的开源代码。

mian.cpp

//_____________________________________________________________________________
// main function
int main (int argc, char **argv)
//-----------------------------------------------------------------------------
{
  MarchingCubes mc ;                // 构造
  mc.set_resolution( 60,60,60 ) ;   // 设置分辨率
  //mc.set_method(true);              // 是否使用原始MC方法
  mc.init_all() ;                   // 初始化
  compute_data( mc ) ;              // 生成模拟数据,调用 mc.set_data()
  mc.run() ;                        // 主函数
  mc.clean_temps() ;                // 清除临时变量

  mc.writePLY("test.ply") ;         // 保存结果
  mc.clean_all() ;                  // 析构

  return 0 ;
}
//_____________________________________________________________________________

这位大佬写的代码相当规范且有非常详尽的注释,考虑篇幅,在下面仅展示关键部分

MarchingCubes.h


typedef unsigned char uchar ;
typedef   signed char schar ;
typedef        float real  ;


class MarchingCubes
//-----------------------------------------------------------------------------
{
// 构造函数
public :
  
  /** 主构造函数和默认构造函数*/
   MarchingCubes ( const int size_x = -1, const int size_y = -1, const int size_z = -1 ) ;
  
  /** 析构函数 */
  ~MarchingCubes() ;

//-----------------------------------------------------------------------------
// 访问器
public :

  /**
   * 修改网格的大小
   * \param size_x 网格的宽度
   * \param size_y 网格的深度
   * \param size_z 网格的高度
   */
  inline void set_resolution( const int size_x, const int size_y, const int size_z ) 
  { 
      _size_x = size_x ;  
      _size_y = size_y ;  
      _size_z = size_z ; 
  }

  /**
   * 选择是否使用增强的拓扑控制查找表或原始的 Marching Cubes 算法
   * \param originalMC 如果为 true,则使用原始的 Marching Cubes
   */
  inline void set_method ( const bool originalMC = false ) { _originalMC = originalMC ; }


  /**
   * 设置网格中指定立方体的数据
   * \param val 新的立方体值
   * \param i 立方体的横坐标
   * \param j 立方体的纵坐标
   * \param k 立方体的高度
   */
  inline void set_data ( const real val, const int i, const int j, const int k ) 
  { 
      _data[ i + j*_size_x + k*_size_x*_size_y] = val ; 
  }
  
  // 数据初始化
 
  /** 初始化临时结构(必须在调用前设置大小):包括网格和每个立方体的顶点索引 */
  void init_temps(); 
  /** 初始化所有结构(必须在调用前设置大小):包括临时结构和网格缓存 */
  void init_all();
  /**  清除临时结构:包括网格和主要结构 */
  void clean_temps();
  /** 清除所有结构:包括临时结构和网格缓存 */
  void clean_all();

//-----------------------------------------------------------------------------
// 算法
public :
  /**
   * 主算法:必须在调用 init_all 之后调用
   * \param iso 等值面值
   */
  void run( real iso = (real)0.0 ) ;

protected :
  /** 处理一个立方体的三角剖分 */
  void process_cube () ;

  /** 测试立方体的三角剖分组件是否应通过模糊面的内部连接 */
  bool test_face ( schar face ) ;

  /** 测试立方体的三角剖分组件是否应通过立方体的内部连接 */
  bool test_interior( schar s ) ;


//-----------------------------------------------------------------------------
// 操作
protected :
  /**
   * 通过沿立方体边缘的插值计算几乎所有网格的顶点
   * \param iso 等值面值
   */
  void compute_intersection_points( real iso ) ;

  /**
   * 添加三角形到网格的例程
   * \param trig 三角形的代码作为边索引的序列
   * \param n 需要生成的三角形数量
   * \param v12 使用的内部顶点的索引(如有必要)
   */
  void add_triangle ( const char* trig, char n, int v12 = -1 ) ;

  /** 测试并在需要时增加顶点缓存的容量以插入新顶点 */
  void test_vertex_addition() ;

  /** 在当前水平边上添加顶点 */
  int add_x_vertex() ;
  //yz
  
  /** 在当前立方体内添加顶点 */
  int add_c_vertex() ;

  /**
   * 插值指定立方体下顶点的隐函数的水平梯度
   * \param i 立方体的横坐标
   * \param j 立方体的纵坐标
   * \param k 立方体的高度
   */
  real get_x_grad( const int i, const int j, const int k ) const ;
  //yz
  
    /**
   * 获取指定立方体下水平边的预计算顶点索引
   * \param i 立方体的横坐标
   * \param j 立方体的纵坐标
   * \param k 立方体的高度
   */
  inline int get_x_vert( const int i, const int j, const int k ) const 
  { 
      return _x_verts[ i + j*_size_x + k*_size_x*_size_y] ; 
  }
  //yz
  
  /**
   * 设置特定立方体下水平边的预计算顶点索引
   * \param val 新顶点的索引
   * \param i 立方体的横坐标
   * \param j 立方体的纵坐标(平面内的纵向)
   * \param k 立方体的高度(深度方向的坐标)
   */
  inline void set_x_vert(const int val, const int i, const int j, const int k)
  {
    _x_verts[i + j * _size_x + k * _size_x * _size_y] = val;
  }
   //yz

 
//-----------------------------------------------------------------------------
// 元素
protected :
  bool      _originalMC ;   /**< 选择算法是使用增强的拓扑控制查找表还是使用原始的 Marching Cubes 算法 */
  bool      _ext_data   ;   /**< 选择是分配数据还是使用来自其他类的数据 */

  int       _size_x     ;  /**< 网格的宽度 */
  int       _size_y     ;  /**< 网格的深度 */
  int       _size_z     ;  /**< 网格的高度 */
  real     *_data       ;  /**< 在网格上采样的隐函数值 */

  int      *_x_verts    ;  /**< 预计算的每个立方体下水平边的顶点索引 */
  int      *_y_verts    ;  /**< 预计算的每个立方体下纵向边的顶点索引 */
  int      *_z_verts    ;  /**< 预计算的每个立方体下垂直边的顶点索引 */

  int       _nverts     ;  /**< 顶点缓存中已分配的顶点数量 */
  int       _ntrigs     ;  /**< 三角形缓存中已分配的三角形数量 */
  int       _Nverts     ;  /**< 顶点缓存的大小 */
  int       _Ntrigs     ;  /**< 三角形缓存的大小 */
  Vertex   *_vertices   ;  /**< 顶点缓存 */
  Triangle *_triangles  ;  /**< 三角形缓存 */

  int       _i          ;  /**< 当前活动立方体的横坐标-对应x */
  int       _j          ;  /**< 当前活动立方体的高度-对应y */
  int       _k          ;  /**< 当前活动立方体的纵坐标-对应z */

  real      _cube[8]    ;  /**< 当前活动立方体上的隐函数值 */
  uchar     _lut_entry  ;  /**< 立方体的符号表示,范围在 [0..255] */
  uchar     _case       ;  /**< 当前活动立方体的案例,范围在 [0..15] */
  uchar     _config     ;  /**< 当前活动立方体的配置 */
  uchar     _subconfig  ;  /**< 当前活动立方体的子配置 */
};


MarchingCubes.cpp

//-----------------------------------------------------------------------------
// 初始化临时结构(调用前需要设置大小)
void MarchingCubes::init_temps()
//-----------------------------------------------------------------------------
{
  if( !_ext_data )  // 如果没有使用外部数据
    _data    = new real [_size_x * _size_y * _size_z] ;  // 分配网格数据的内存
  _x_verts = new int  [_size_x * _size_y * _size_z] ;  // 分配用于存储水平边顶点索引的数组
  _y_verts = new int  [_size_x * _size_y * _size_z] ;  // 分配用于存储纵向边顶点索引的数组
  _z_verts = new int  [_size_x * _size_y * _size_z] ;  // 分配用于存储垂直边顶点索引的数组

  memset( _x_verts, -1, _size_x * _size_y * _size_z * sizeof( int ) ) ;  // 初始化水平边顶点索引数组
  memset( _y_verts, -1, _size_x * _size_y * _size_z * sizeof( int ) ) ;  // 初始化纵向边顶点索引数组
  memset( _z_verts, -1, _size_x * _size_y * _size_z * sizeof( int ) ) ;  // 初始化垂直边顶点索引数组
}
//_____________________________________________________________________________



//_____________________________________________________________________________
// 初始化所有结构(调用前需要设置大小)
void MarchingCubes::init_all ()
//-----------------------------------------------------------------------------
{
  init_temps() ;  // 初始化临时结构

  _nverts = _ntrigs = 0 ;  // 初始化顶点数和三角形数为 0
  _Nverts = _Ntrigs = ALLOC_SIZE ;  // 设置顶点缓冲区和三角形缓冲区的初始大小(#define ALLOC_SIZE 65536)
  _vertices  = new Vertex  [_Nverts] ;  // 分配顶点缓冲区的内存
  _triangles = new Triangle[_Ntrigs] ;  // 分配三角形缓冲区的内存
}
//_____________________________________________________________________________

在以下函数中,根据等效旧代码可以明显看出for循环内是在确定每个cube的8bit编码(已经转为十进制)即所谓的查找表入口(_lut_entry),再根据编码找到相应的案例,再经过拓扑验证等操作得到每个cube的三角近似

//_____________________________________________________________________________
// 主算法
void MarchingCubes::run( real iso )
//-----------------------------------------------------------------------------
{
  clock_t time = clock() ;  // 记录开始时间

  compute_intersection_points( iso ) ;  // 计算网格中每个立方体边界上的交点

  // 遍历三维网格中的每个立方体
  for( _k = 0 ; _k < _size_z-1 ; _k++ )
  for( _j = 0 ; _j < _size_y-1 ; _j++ )
  for( _i = 0 ; _i < _size_x-1 ; _i++ )
  {
    _lut_entry = 0 ;  // 初始化查找表的入口值
    for( int p = 0 ; p < 8 ; ++p )
    {
      // 计算当前立方体的每个顶点相对于等值面的值
      _cube[p] = get_data( _i+((p^(p>>1))&1), _j+((p>>1)&1), _k+((p>>2)&1) ) - iso ;
      if( fabs( _cube[p] ) < FLT_EPSILON ) _cube[p] = FLT_EPSILON ;  // 如果值接近零,设置为最小浮点值
      if( _cube[p] > 0 ) _lut_entry += 1 << p ;  // 根据顶点值更新查找表入口
    }

    /*
    // 以下是等效的旧代码,每个顶点的计算逐一展开
    if( ( _cube[0] = get_data( _i , _j , _k ) ) > 0 ) _lut_entry +=   1 ;
    if( ( _cube[1] = get_data(_i+1, _j , _k ) ) > 0 ) _lut_entry +=   2 ;
    if( ( _cube[2] = get_data(_i+1,_j+1, _k ) ) > 0 ) _lut_entry +=   4 ;
    if( ( _cube[3] = get_data( _i ,_j+1, _k ) ) > 0 ) _lut_entry +=   8 ;
    if( ( _cube[4] = get_data( _i , _j ,_k+1) ) > 0 ) _lut_entry +=  16 ;
    if( ( _cube[5] = get_data(_i+1, _j ,_k+1) ) > 0 ) _lut_entry +=  32 ;
    if( ( _cube[6] = get_data(_i+1,_j+1,_k+1) ) > 0 ) _lut_entry +=  64 ;
    if( ( _cube[7] = get_data( _i ,_j+1,_k+1) ) > 0 ) _lut_entry += 128 ;
    */

    process_cube() ;  // 处理当前立方体,生成对应的三角面片
  }

  // 打印算法运行时间
  printf("Marching Cubes ran in %lf secs.\n", (double)(clock() - time)/CLOCKS_PER_SEC) ;
}
//_____________________________________________________________________________

根据上面顶点的编码(第一张截图),就可以理解为什么以下过程中只处理顶点0、1、3、4
下面的代码的大意就是,循环每个处理cube的单位轴,但凡轴边两端点正负值不同,则在相应边上添加顶点

//-----------------------------------------------------------------------------
// 计算等值面交点
void MarchingCubes::compute_intersection_points( real iso )
//-----------------------------------------------------------------------------
{
  for( _k = 0 ; _k < _size_z ; _k++ )  // 遍历 z 方向
  for( _j = 0 ; _j < _size_y ; _j++ )  // 遍历 y 方向
  for( _i = 0 ; _i < _size_x ; _i++ )  // 遍历 x 方向
  {
    _cube[0] = get_data( _i, _j, _k ) - iso ;  // 获取当前顶点的值并减去等值面值

    if( _i < _size_x - 1 )  // 如果不是x方向的最后一个cube
      _cube[1] = get_data(_i+1, _j , _k ) - iso ;  // 获取下一顶点的值并减去等值面值
    else  // x方向的最后一个cube
      _cube[1] = _cube[0] ;  // 使用上一顶点的值

    if( _j < _size_y - 1 )  // 如果不是y方向的最后一个cube
      _cube[3] = get_data( _i ,_j+1, _k ) - iso ;  // 获取下一顶点的值并减去等值面值
    else  // 如果是
      _cube[3] = _cube[0] ;  // 使用上一顶点的值

    if( _k < _size_z - 1 )  // 如果不是z方向的最后一个cube
      _cube[4] = get_data( _i , _j ,_k+1) - iso ;  // 获取下一顶点的值并减去等值面值
    else  // 如果是
      _cube[4] = _cube[0] ;  // 使用上一顶点的值

    if( fabs( _cube[0] ) < FLT_EPSILON ) _cube[0] = FLT_EPSILON ;  // 如果接近于零,则设置为最小正数
    if( fabs( _cube[1] ) < FLT_EPSILON ) _cube[1] = FLT_EPSILON ;
    if( fabs( _cube[3] ) < FLT_EPSILON ) _cube[3] = FLT_EPSILON ;
    if( fabs( _cube[4] ) < FLT_EPSILON ) _cube[4] = FLT_EPSILON ;

    if( _cube[0] < 0 )  // 如果当前顶点的值小于等值面值
    {
    // 如果三个方向的下一顶点的值大于等值面值,则在相应边上添加顶点
      if( _cube[1] > 0 ) set_x_vert( add_x_vertex( ), _i,_j,_k ) ;  
      if( _cube[3] > 0 ) set_y_vert( add_y_vertex( ), _i,_j,_k ) ;  
      if( _cube[4] > 0 ) set_z_vert( add_z_vertex( ), _i,_j,_k ) ; 
    }
    else  // 如果当前顶点的值大于等于等值面值
    {
    // 如果三个方向的下一顶点的值小于等值面值,则在相应边上添加顶点
      if( _cube[1] < 0 ) set_x_vert( add_x_vertex( ), _i,_j,_k ) ;  
      if( _cube[3] < 0 ) set_y_vert( add_y_vertex( ), _i,_j,_k ) ;  
      if( _cube[4] < 0 ) set_z_vert( add_z_vertex( ), _i,_j,_k ) ;  
    }
  }
}
//_____________________________________________________________________________

根据案例编码确定案例,再通过配置(复杂的案例需要子配置)保证正确的拓扑,部分代码太长了被省略,这里也不一一看各种案例的具体情况了

//_____________________________________________________________________________
// 处理单位立方体
void MarchingCubes::process_cube( )
//-----------------------------------------------------------------------------
{
  // 如果启用了经典的Marching Cubes模式
  if( _originalMC )
  {
    char nt = 0 ;
    // 计算当前查找表中三角形的数量
    while( casesClassic[_lut_entry][3*nt] != -1 ) nt++ ;
    // 添加三角形
    add_triangle( casesClassic[_lut_entry], nt ) ;
    return ;
  }

  int   v12 = -1 ;   // 用于存储生成的额外顶点索引
  _case   = cases[_lut_entry][0] ;   // 获取当前立方体的拓扑情况(最大值14)
  _config = cases[_lut_entry][1] ;   // 获取当前立方体的配置(最大值47)
  _subconfig = 0 ;   // 初始化子配置

  switch( _case )
  {
  case  0 : // 情况0:无需处理
    break ;

  case  1 : // 情况1:添加一个三角形
    add_triangle( tiling1[_config], 1 ) ;
    break ;

  case  2 : // 情况2:添加两个三角形
    add_triangle( tiling2[_config], 2 ) ;
    break ;

  case  3 : // 情况3:根据面测试的结果选择不同的三角形配置
    if( test_face( test3[_config]) )
      add_triangle( tiling3_2[_config], 4 ) ; // 3.2 配置
    else
      add_triangle( tiling3_1[_config], 2 ) ; // 3.1 配置
    break ;

  case  4 : // 情况4:根据内部测试的结果选择不同的三角形配置
    if( test_interior( test4[_config]) )
      add_triangle( tiling4_1[_config], 2 ) ; // 4.1.1 配置
    else
      add_triangle( tiling4_2[_config], 6 ) ; // 4.1.2 配置
    break ;

  case  5 :
    add_triangle( tiling5[_config], 3 ) ;
    break ;

  case  6 :
    if( test_face( test6[_config][0]) )
      add_triangle( tiling6_2[_config], 5 ) ; // 6.2
    else
    {
      if( test_interior( test6[_config][1]) )
        add_triangle( tiling6_1_1[_config], 3 ) ; // 6.1.1
      else
	  {
        v12 = add_c_vertex() ;
        add_triangle( tiling6_1_2[_config], 9 , v12) ; // 6.1.2
      }
    }
    break ;

  case  7 :
    if( test_face( test7[_config][0] ) ) _subconfig +=  1 ;
    if( test_face( test7[_config][1] ) ) _subconfig +=  2 ;
    if( test_face( test7[_config][2] ) ) _subconfig +=  4 ;
    switch( _subconfig )
      {
      case 0 :
        add_triangle( tiling7_1[_config], 3 ) ; break ;
      // ......
      case 7 :
        if( test_interior( test7[_config][3]) )
          add_triangle( tiling7_4_2[_config], 9 ) ;
        else
          add_triangle( tiling7_4_1[_config], 5 ) ;
        break ;
      };
    break ;

  case  8 :
    add_triangle( tiling8[_config], 2 ) ;
    break ;

  case  9 :
    add_triangle( tiling9[_config], 4 ) ;
    break ;

  case 10 :
    if( test_face( test10[_config][0]) )
    {
      if( test_face( test10[_config][1]) )
        add_triangle( tiling10_1_1_[_config], 4 ) ; // 10.1.1
      else
      {
        v12 = add_c_vertex() ;
        add_triangle( tiling10_2[_config], 8, v12 ) ; // 10.2
      }
    }
    else
    {
      if( test_face( test10[_config][1]) )
      {
        v12 = add_c_vertex() ;
        add_triangle( tiling10_2_[_config], 8, v12 ) ; // 10.2
      }
      else
      {
        if( test_interior( test10[_config][2]) )
          add_triangle( tiling10_1_1[_config], 4 ) ; // 10.1.1
        else
          add_triangle( tiling10_1_2[_config], 8 ) ; // 10.1.2
      }
    }
    break ;

  case 11 :
    add_triangle( tiling11[_config], 4 ) ;
    break ;

  case 12 :
    if( test_face( test12[_config][0]) )
    {
      if( test_face( test12[_config][1]) )
        add_triangle( tiling12_1_1_[_config], 4 ) ; // 12.1.1
      else
      {
        v12 = add_c_vertex() ;
        add_triangle( tiling12_2[_config], 8, v12 ) ; // 12.2
      }
    }
    else
    {
      if( test_face( test12[_config][1]) )
      {
        v12 = add_c_vertex() ;
        add_triangle( tiling12_2_[_config], 8, v12 ) ; // 12.2
      }
      else
      {
        if( test_interior( test12[_config][2]) )
          add_triangle( tiling12_1_1[_config], 4 ) ; // 12.1.1
        else
          add_triangle( tiling12_1_2[_config], 8 ) ; // 12.1.2
      }
    }
    break ;

  case 13 :
    if( test_face( test13[_config][0] ) ) _subconfig +=  1 ;
    if( test_face( test13[_config][1] ) ) _subconfig +=  2 ;
    if( test_face( test13[_config][2] ) ) _subconfig +=  4 ;
    if( test_face( test13[_config][3] ) ) _subconfig +=  8 ;
    if( test_face( test13[_config][4] ) ) _subconfig += 16 ;
    if( test_face( test13[_config][5] ) ) _subconfig += 32 ;
    switch( subconfig13[_subconfig] )
    {
      case 0 :/* 13.1 */
        add_triangle( tiling13_1[_config], 4 ) ; break ;
	  // ......
      case 45 :/* 13.1 */
        add_triangle( tiling13_1_[_config], 4 ) ; break ;

      default :
        printf("Marching Cubes: Impossible case 13?\n" ) ;  print_cube() ;
      }
      break ;

  case 14 :
    add_triangle( tiling14[_config], 4 ) ;
    break ;
  };
}
//_____________________________________________________________________________

test_facetest_interior都是用以确定正确的拓扑,具体思路可见原文

//-----------------------------------------------------------------------------
// 测试一个面
// 如果面大于0,则返回true 表示该面包含等值面的一部分
bool MarchingCubes::test_face( schar face )
//-----------------------------------------------------------------------------
{
  real A, B, C, D;

  switch( face )
  {
  case -1 : case 1 :  A = _cube[0] ;  B = _cube[4] ;  C = _cube[5] ;  D = _cube[1] ;  break ;  // x轴方向的面
  case -2 : case 2 :  A = _cube[1] ;  B = _cube[5] ;  C = _cube[6] ;  D = _cube[2] ;  break ;  // y轴方向的面
  case -3 : case 3 :  A = _cube[2] ;  B = _cube[6] ;  C = _cube[7] ;  D = _cube[3] ;  break ;  // z轴方向的面
  case -4 : case 4 :  A = _cube[3] ;  B = _cube[7] ;  C = _cube[4] ;  D = _cube[0] ;  break ;  // 反x轴方向的面
  case -5 : case 5 :  A = _cube[0] ;  B = _cube[3] ;  C = _cube[2] ;  D = _cube[1] ;  break ;  // 反y轴方向的面
  case -6 : case 6 :  A = _cube[4] ;  B = _cube[7] ;  C = _cube[6] ;  D = _cube[5] ;  break ;  // 反z轴方向的面
  default : printf( "Invalid face code %d\n", face ) ;  print_cube() ;  A = B = C = D = 0 ;
  };

  if( fabs( A*C - B*D ) < FLT_EPSILON )  // 如果AC-BD接近于零
    return face >= 0 ;  // 直接返回face的正负号
  return face * A * ( A*C - B*D ) >= 0 ;  // 如果face和A的符号相同,并且AC-BD大于零,则返回true
}
//_____________________________________________________________________________
//_____________________________________________________________________________
// 测试立方体的内部
// 如果 s == 7,则在内部为空时返回 true
// 如果 s == -7,则在内部为空时返回 false
bool MarchingCubes::test_interior(schar s)
//-----------------------------------------------------------------------------
{
  real t, At=0, Bt=0, Ct=0, Dt=0, a, b;
  char test = 0;
  char edge = -1; // 三角剖分的参考边

  switch (_case)
  {
    case 4:
    case 10:
      // 计算立方体的对角线上的点之间的线性插值
      a = (_cube[4] - _cube[0]) * (_cube[6] - _cube[2]) - (_cube[7] - _cube[3]) * (_cube[5] - _cube[1]);
      b = _cube[2] * (_cube[4] - _cube[0]) + _cube[0] * (_cube[6] - _cube[2])
          - _cube[1] * (_cube[7] - _cube[3]) - _cube[3] * (_cube[5] - _cube[1]);
      t = -b / (2 * a);

      // 判断 t 是否在合法范围内
      if (t < 0 || t > 1) return s > 0;

      // 计算插值点的坐标
      At = _cube[0] + (_cube[4] - _cube[0]) * t;
      Bt = _cube[3] + (_cube[7] - _cube[3]) * t;
      Ct = _cube[2] + (_cube[6] - _cube[2]) * t;
      Dt = _cube[1] + (_cube[5] - _cube[1]) * t;
      break;

    case 6:
    case 7:
    case 12:
    case 13:
      // 根据 _case 设置参考边
      switch (_case)
      {
        case 6: edge = test6[_config][2]; break;
        case 7: edge = test7[_config][4]; break;
        case 12: edge = test12[_config][3]; break;
        case 13: edge = tiling13_5_1[_config][_subconfig][0]; break;
      }

      // 根据参考边计算插值点的坐标
      switch (edge)
      {
        case 0:
          t = _cube[0] / (_cube[0] - _cube[1]);
          At = 0;
          Bt = _cube[3] + (_cube[2] - _cube[3]) * t;
          Ct = _cube[7] + (_cube[6] - _cube[7]) * t;
          Dt = _cube[4] + (_cube[5] - _cube[4]) * t;
          break;
        case 1:
          t = _cube[1] / (_cube[1] - _cube[2]);
          At = 0;
          Bt = _cube[0] + (_cube[3] - _cube[0]) * t;
          Ct = _cube[4] + (_cube[7] - _cube[4]) * t;
          Dt = _cube[5] + (_cube[6] - _cube[5]) * t;
          break;
        case 2:
          t = _cube[2] / (_cube[2] - _cube[3]);
          At = 0;
          Bt = _cube[1] + (_cube[0] - _cube[1]) * t;
          Ct = _cube[5] + (_cube[4] - _cube[5]) * t;
          Dt = _cube[6] + (_cube[7] - _cube[6]) * t;
          break;
        case 3:
          t = _cube[3] / (_cube[3] - _cube[0]);
          At = 0;
          Bt = _cube[2] + (_cube[1] - _cube[2]) * t;
          Ct = _cube[6] + (_cube[5] - _cube[6]) * t;
          Dt = _cube[7] + (_cube[4] - _cube[7]) * t;
          break;
        case 4:
          t = _cube[4] / (_cube[4] - _cube[5]);
          At = 0;
          Bt = _cube[7] + (_cube[6] - _cube[7]) * t;
          Ct = _cube[3] + (_cube[2] - _cube[3]) * t;
          Dt = _cube[0] + (_cube[1] - _cube[0]) * t;
          break;
        case 5:
          t = _cube[5] / (_cube[5] - _cube[6]);
          At = 0;
          Bt = _cube[4] + (_cube[7] - _cube[4]) * t;
          Ct = _cube[0] + (_cube[3] - _cube[0]) * t;
          Dt = _cube[1] + (_cube[2] - _cube[1]) * t;
          break;
        case 6:
          t = _cube[6] / (_cube[6] - _cube[7]);
          At = 0;
          Bt = _cube[5] + (_cube[4] - _cube[5]) * t;
          Ct = _cube[1] + (_cube[0] - _cube[1]) * t;
          Dt = _cube[2] + (_cube[3] - _cube[2]) * t;
          break;
        case 7:
          t = _cube[7] / (_cube[7] - _cube[4]);
          At = 0;
          Bt = _cube[6] + (_cube[5] - _cube[6]) * t;
          Ct = _cube[2] + (_cube[1] - _cube[2]) * t;
          Dt = _cube[3] + (_cube[0] - _cube[3]) * t;
          break;
        case 8:
          t = _cube[0] / (_cube[0] - _cube[4]);
          At = 0;
          Bt = _cube[3] + (_cube[7] - _cube[3]) * t;
          Ct = _cube[2] + (_cube[6] - _cube[2]) * t;
          Dt = _cube[1] + (_cube[5] - _cube[1]) * t;
          break;
        case 9:
          t = _cube[1] / (_cube[1] - _cube[5]);
          At = 0;
          Bt = _cube[0] + (_cube[4] - _cube[0]) * t;
          Ct = _cube[3] + (_cube[7] - _cube[3]) * t;
          Dt = _cube[2] + (_cube[6] - _cube[2]) * t;
          break;
        case 10:
          t = _cube[2] / (_cube[2] - _cube[6]);
          At = 0;
          Bt = _cube[1] + (_cube[5] - _cube[1]) * t;
          Ct = _cube[0] + (_cube[4] - _cube[0]) * t;
          Dt = _cube[3] + (_cube[7] - _cube[3]) * t;
          break;
        case 11:
          t = _cube[3] / (_cube[3] - _cube[7]);
          At = 0;
          Bt = _cube[2] + (_cube[6] - _cube[2]) * t;
          Ct = _cube[1] + (_cube[5] - _cube[1]) * t;
          Dt = _cube[0] + (_cube[4] - _cube[0]) * t;
          break;
        default:
          printf("无效的边 %d\n", edge);
          print_cube();
          break;
      }
      break;

    default:
      printf("无效的模糊情况 %d\n", _case);
      print_cube();
      break;
  }

  // 根据计算结果判断立方体内部的状态
  if (At >= 0) test++;
  if (Bt >= 0) test += 2;
  if (Ct >= 0) test += 4;
  if (Dt >= 0) test += 8;

  switch (test)
  {
    case 0: return s > 0;
    case 1: return s > 0;
    case 2: return s > 0;
    case 3: return s > 0;
    case 4: return s > 0;
    case 5: if (At * Ct - Bt * Dt < FLT_EPSILON) return s > 0; break;
    case 6: return s > 0;
    case 7: return s < 0;
    case 8: return s > 0;
    case 9: return s > 0;
    case 10: if (At * Ct - Bt * Dt >= FLT_EPSILON) return s > 0; break;
    case 11: return s < 0;
    case 12: return s > 0;
    case 13: return s < 0;
    case 14: return s < 0;
    case 15: return s < 0;
  }

  return s < 0;
}
//_____________________________________________________________________________

看一下是如何添加三角形的,trig为三角形数组,每三个边索引表示一个三角形如上面的图,nt为三角形个数
注意tv中是在init_temps中预计算的顶点索引(顶点数组在compute_intersection_points计算交点时就建好了)

void MarchingCubes::add_triangle(const char* trig, char n, int v12)
{
    int tv[3]; // 存储三角形的三个顶点

    for (int t = 0; t < 3 * n; t++)
    {
        switch (trig[t])
        {
            case 0: tv[t % 3] = get_x_vert(_i, _j, _k); break;
            case 1: tv[t % 3] = get_y_vert(_i + 1, _j, _k); break;
            case 2: tv[t % 3] = get_x_vert(_i, _j + 1, _k); break;
            case 3: tv[t % 3] = get_y_vert(_i, _j, _k); break;
            case 4: tv[t % 3] = get_x_vert(_i, _j, _k + 1); break;
            case 5: tv[t % 3] = get_y_vert(_i + 1, _j, _k + 1); break;
            case 6: tv[t % 3] = get_x_vert(_i, _j + 1, _k + 1); break;
            case 7: tv[t % 3] = get_y_vert(_i, _j, _k + 1); break;
            case 8: tv[t % 3] = get_z_vert(_i, _j, _k); break;
            case 9: tv[t % 3] = get_z_vert(_i + 1, _j, _k); break;
            case 10: tv[t % 3] = get_z_vert(_i + 1, _j + 1, _k); break;
            case 11: tv[t % 3] = get_z_vert(_i, _j + 1, _k); break;
            case 12: tv[t % 3] = v12; break;
            default: break;
        }

        // 检查顶点是否合法
        if (tv[t % 3] == -1)
        {
            printf("Marching Cubes: invalid triangle %d\n", _ntrigs + 1);
            print_cube();
        }

        // 每三次处理一个三角形
        if (t % 3 == 2)
        {
            // 如果当前三角形数组已满,则扩展数组    ***这个过程涉及到内存分配和拷贝,比较耗时
            if (_ntrigs >= _Ntrigs)
            {
                Triangle *temp = _triangles;
                _triangles = new Triangle[2 * _Ntrigs];
                memcpy(_triangles, temp, _Ntrigs * sizeof(Triangle));
                delete[] temp;
                printf("%d allocated triangles\n", _Ntrigs);
                _Ntrigs *= 2;
            }

            // 添加三角形到数组
            Triangle *T = _triangles + _ntrigs++;
            T->v1 = tv[0];
            T->v2 = tv[1];
            T->v3 = tv[2];
        }
    }
}

导出时,将顶点数组和三角形数组按相应格式写出就可以了


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2076667.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

两个实用的Python编程技巧

一、变量类型声明技巧 虽然在Python中可以不用声明变量的类型&#xff0c;但是为了加快程序的运算速度&#xff0c;减少不必要的bug&#xff0c;我们可以在定义变量之初就把它的类型确定&#xff0c;这样可以更好地传输变量值。如下面的例子。 我们定义了两个变量&#xff0c…

IO进程线程 240826作业

作业 创建3个进程,子进程1拷贝文件的前一半,子进程2拷贝后一半文件,父进程回收两个子进程资源。 将1.txt内容拷贝到2.txt中 #include <myhead.h> int main(int argc, const char *argv[]) {pid_t pid1;pid1 fork();int fd1 open("./1.txt",O_RDWR);if(fd1…

JavaWeb JavaScript ④ JS的对象和JSON

只要你的风格是适应客观规律的&#xff0c;那你以后会越来越好&#xff0c;做事情会越来越顺利 —— 24.8.26 一、JS创建对象 语法 方式1 new Object() 方式2 {属性名&#xff1a;属性值&#xff0c;… …&#xff0c;函数名&#xff1a;function(){}} 方式…

Python | Leetcode Python题解之第371题两整数之和

题目&#xff1a; 题解&#xff1a; MASK1 4294967296 # 2^32 MASK2 2147483648 # 2^31 MASK3 2147483647 # 2^31-1class Solution:def getSum(self, a: int, b: int) -> int:a % MASK1b % MASK1while b ! 0:carry ((a & b) << 1) % MASK1a (a ^ b) % MA…

Agent实际落地的应用 未来生活的无形助手

在这个信息爆炸的时代&#xff0c;我们每个人都在追求更高效的生活方式。想象一下&#xff0c;如果有一个无形的助手&#xff0c;能够理解我们的需求&#xff0c;自动处理繁琐的任务&#xff0c;甚至为我们提供个性化的建议&#xff0c;那将是多么美好的体验&#xff01;这正是…

线性DP经典题型

数字三角形&#x1f342; #include<bits/stdc.h> using namespace std; int main() {int n;cin>>n;vector<vector<int>>arr(n1,vector<int>(n1,0));for(int i 1;i<n;i){for(int j 1;j<i;j){cin>>arr[i][j];}}vector<vector<i…

【精选】分享9款AI毕业论文生成初稿题目网站

在当今学术研究领域&#xff0c;AI技术的应用日益广泛&#xff0c;尤其是在学术论文的撰写过程中。AI论文生成器的出现&#xff0c;极大地简化了学术写作流程&#xff0c;提高了写作效率。以下是9款推荐的AI毕业论文生成初稿的网站&#xff0c;它们各有特色&#xff0c;能够满足…

【Qt】多元素控件QTableWidget

多元素控件QTableWidget 使用QTableWidget表示一个表格控件&#xff0c;一个表格中包含若干行、每一个行又包含若干列。 表格中的每一个单元格&#xff0c;都是一个QTableWidget对象。 QTableWidget核心方法 方法说明 item(int row, int column) 根据⾏数列数获取指定的 Q…

【电路笔记】-运算放大器基础

运算放大器基础 文章目录 运算放大器基础1、概述2、运算放大器表示3、开环增益(Open-Loop Gain)4、输入和输出阻抗5、带宽6、偏移电压7、理想运算放大器模型7.1 饱和模式7.2 线性模式8、实际运算放大器9、总结1、概述 本文将介绍运算放大器(也称为运算放大器,Operational …

HandBrakeCLI 压缩工具的简单实用

HandBrakeCLI -i input.mp4 -o output.mp4 --encoder qsv_h264 -b 500k --preset "Android 576p25" --width 320 --height 576 --quiet--encoder qsv_h264 意思代表inter的gpu编码 -b 500k 设置比特率 --preset "Android 576p25" 设置预设 --width 320 --…

揭秘OTP与MTP:你的存储小秘密,一次性和多次可编程大不同!

NVM,即非易失性存储器,是一种非易失性内存。 NVM的特点是存储的数据在断电后不会消失。传统的NVM,如掩模ROM、可编程ROM(PROM)、可擦除可编程ROM(EPROM)、电可擦可编程ROM(EEPROM)、NAND/NOR闪存等,以及目前正在开发的许多新型状态存储器,如磁性存储器(MRAM)、电…

Jar包导入本地maven仓库

当jar包未引入到公共maven仓库时&#xff0c;直接通过maven坐标的方式引入会报错&#xff0c;找不到该依赖。所以可以将jar包导入到本地maven仓库&#xff0c;再通过maven坐标引入后就没有问题。 mvn install:install-file -Dfilexxxxxx.jar -DgroupIdcom.xx -DartifactIdxxxx…

idea debug 各个步骤含义

基本功能 IntelliJ IDEA 的 Debug 功能提供了强大的调试支持&#xff0c;允许开发者逐步执行代码&#xff0c;检查变量值&#xff0c;评估表达式等。以下是 Debug 模式中常见的几个按钮及其含义&#xff1a; Show Execution Point (显示执行点)&#xff1a;将光标跳转到当前正在…

如何使用ssm实现模具制造企业订单跟踪管理系统+vue

TOC ssm256模具制造企业订单跟踪管理系统vue 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关数据进行科学化&#xff0c;…

编程仙尊——深入理解指针(1)

目录 1.认识指针 2.指针变量和地址 2.1取地址操作符&#xff08;&&#xff09; 2.2 指针变量和解引用操作符 2.3 指针变量的大小 3.指针类型的意义 3.1指针的解引用 3.2 指针-整数 3.3 void*指针 1、认识指针 在生活中&#xff0c;一栋楼的每个房间都会有房间号 …

OpenGL实现3D游戏编程【连载5】——纹理坐标、纹理贴图

OpenGL实现3D游戏编程【连载5】——纹理坐标、纹理贴图 欢迎来到zhooyu的专栏。 个人主页&#xff1a;【zhooyu】 文章专栏&#xff1a;【OpenGL实现3D游戏编程】 本专栏内容&#xff1a; 我们从游戏的角度出发&#xff0c;用C去了解一下游戏中的功能都是怎么实现的。这一切还…

六、前后端分离通用权限系统(6)

&#x1f33b;&#x1f33b; 目录 一、用户管理1.1、代码生成器1.2、用户管理后端 CRUD1.2.1、controller1.2.2、service 接口1.2.3、service 接口实现1.2.4、mapper1.2.5、xml1.2.6、knife4j 测试 1.3、用户管理前端 CRUD1.3.1、添加路由1.3.2、定义基础 api1.3.3、实现页面功…

架构设计(5)服务网格(Service Mesh)

服务网格&#xff08;Service Mesh&#xff09;是一个专门设计的基础设施层&#xff0c;用于管理和处理微服务架构中服务间的通信。服务网格通过在服务间插入代理&#xff0c;提供了一种透明的方式来控制、监控和管理服务之间的流量。以下是关于服务网格的详细介绍&#xff0c;…

( 基于SystemView软件)AM调制与解调仿真实验

一、实验目的&#xff1a; 熟悉使用SystemView软件&#xff0c;了解各部分功能模块的操作和使用方法。 通过实验进一步观察、了解模拟信号AM调制、解调原理。 掌握AM调制信号的主要性能指标。 比较、理解AM调制的相干解调原理。 二、实验器材&#xff1a; 装有SystemView…

【有道云-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…