基于C++(MFC)的二维Delaunay三角剖分与Voronoi图的算法及代码

本文详细介绍Delaunay三角网的特性和构建算法,包括空圆性、最接近性、唯一性、最优性、最规则性、区域性和凸多边形外壳。并探讨了Voronoi图的定义、特点及其在地理学、气象学等领域的广泛应用。文章还介绍了Delaunay三角剖分算法如何应用于Voronoi图的构建。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、 Delaunay三角网

  1. Delaunay三角网的特性:

(1)空圆性,任一三角形外接圆内部不包含其他点。
(2)最接近:以最近临的三点形成三角形,且各线段(三角形的边)皆不相交。
(3)唯一性:不论从区域何处开始构建,最终都将得到一致的结果。
(4)最优性:任意两个相邻三角形形成的凸四边形的对角线如果可以互换的话,那么两个三角形六个内角中最小的角度不会变大。
(5)最规则:如果将三角网中的每个三角形的最小角进行升序排列,则Delaunay三角网的排列得到的数值最大。
(6)区域性:新增、删除、移动某一个顶点时只会影响临近的三角形。
(7)具有凸多边形的外壳:三角网最外层的边界形成一个凸多边形的外壳。

  1. 算法

Delaunay剖分是一种三角剖分的标准,实现它有多种算法。本次采用Bowyer-Watson算法,算法的基本步骤是:
(1) 构造一个超级三角形,包含所有散点,放入三角形链表。
(2)将点集中的散点依次插入,在三角形链表中找出其外接圆包含
插入点的三角形(称为该点的影响三角形),删除影响三角形的公共边,将插入点同影响三角形的全部顶点连接起来,从而完成一个点在Delaunay三角形链表中的插入。
(3)根据优化准则对局部新形成的三角形进行优化。将形成的三角形放入Delaunay三角形链表。
(4)循环执行上述第2步,直到所有散点插入完毕。
(5)删除和超级三角形有关的三角形,形成凸包。

在这里插入图片描述
二、Voronoi Diagram

  1. Voronoi图的定义
    又叫泰森多边形或Dirichlet图,它是由一组由连接两邻点直线的垂直平分线组成的连续多边形组成。
  2. Voronoi图的特点
    (1)每个V多边形内有一个生成元;
    (2)每个V多边形内点到该生成元距离短于到其它生成元距离;
    (3)多边形边界上的点到生成此边界的生成元距离相等;
    (4)邻接图形的Voronoi多边形界线以原邻接界线作为子集。
  3. Voronoi的应用
    在计算几何学科中的重要地位,由于其根据点集划分的区域到点的距离最近的特点,其在地理学、气象学、结晶学、航天、核物理学、机器人等领域具有广泛的应用。如在障碍物点集中,规避障碍寻找最佳路径。
  4. 建立Voronoi图的方法
    V图有着按距离划分邻近区域的普遍特性,应用范围广。生成V图的方法很多,常见的有分治法、扫描线算法和Delaunay三角剖分算法。
    本次实验采用的是Delaunay三角剖分算法。主要是指生成Voronoi图时先生成其对偶元Delaunay三角网,再找出三角网每一三角形的外接圆圆心,最后连接相邻三角形的外接圆圆心,形成以每一三角形顶点为生成元的多边形网。如下图所示(来源于网络,如有侵权,请联系删除):
    在这里插入图片描述
    建立Voronoi图算法的关键是对离散数据点合理地连成三角网,即构建Delaunay三角网。
  5. 建立Voronoi图的步骤

(1)离散点自动构建三角网,即构建Delaunay三角网。对离散点和形成的三角形编号,记录每个三角形是由哪三个离散点构成的。
(2)计算每个三角形的外接圆圆心,并记录之。
(3)遍历三角形链表,寻找与当前三角形pTri三边共边的相邻三角形TriA,TriB和TriC。
(4)如果找到,则把寻找到的三角形的外心与pTri的外心连接,存入维诺边链表中。如果找不到,则求出最外边的中垂线射线存入维诺边链表中。
(5)遍历结束,所有维诺边被找到,根据边画出维诺图。

生成效果图:
在这里插入图片描述
相关代码下载链接:(等课程作业上交完,再分享吧,如有紧急需要,请联系我)
三、代码解析

  1. 新建项目
    新建一个MFC程序项目,项目名Voronoi2。

  2. 资源视图的菜单设置
    Voronoi图的生成主要分三步,鼠标点击屏幕获取点,根据点构造Delaunay三角形,根据三角形构造Voronoi图。为了重置视图,还需要增加一个清除屏幕按钮。
    (步骤提示:资源视图—— 在菜单栏——编辑名称——属性——设置ID 。)

    加粗样式
    在这里插入图片描述
    然后给每个菜单添加消息处理程序。在菜单按钮名右击添加。消息【鼠标点击屏幕获取点】是用来为数据开辟空间的,响应函数在Doc里面。因为要显示视图,所以消息【根据点构造Delaunay三角形】,【根据三角形构造Voronoi图】响应函数在View类里面。
    在这里插入图片描述

  3. 封装类

新建类,本程序需要用到点,边和三角形的数据结构,放在自己新建的类头文件里,自己需要的函数,则是边编程边添加。
(步骤提示:解决方案资源管理器——右击——添加类——类名CVoronoidiagram——编辑函数)
在Doc文件里,给自己新建的类实例化对象:

   class CVoronoi2Doc : public CDocument
    {
        (省略.。。。。)
    // 实现
    public:
    	CVoronoidiagram * m_CVor;
    	(省略。。。。)
    	}

只有触发消息【鼠标点击屏幕获取点】,才可以使用类CVoronoidiagram,并为数据开辟空间。
在这里插入图片描述

在CPP文件里,对新建的指针对象,构造函数进行初始化,析构函数进行释放
在这里插入图片描述

鼠标点击屏幕,获取点,并且在屏幕显示出来。先添加一个鼠标点击屏幕的触发消息响应。我这篇文章有详细操作,可以借鉴。
https://blog.csdn.net/weixin_44210987/article/details/90679214
//鼠标点击屏幕获取点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
具体代码如下
Vec.h

#ifndef VEC_H
#define VEC_H
/*
Szymon Rusinkiewicz
Princeton University

Vec.h
Class for a constant-length vector

Supports the following operations:
	vec v1;			// Initialized to (0,0,0)
	vec v2(1,2,3);		// Initialized to (1,2,3)
	vec v3(v2);		// Copy constructor
	float farray[3];
	vec v4 = vec(farray);	// Explicit: "v4 = farray" won't work
	Vec<3,double> vd;	// The "vec" used above is Vec<3,float>
	point p1, p2, p3;	// Same as vec

	v3 = v1 + v2;		// Also -, *, /  (all componentwise)
	v3 = 3.5f * v1;		// Also vec * scalar, vec / scalar
				// NOTE: scalar has to be the same type:
				// it won't work to do double * vec<float>
	v1 = min(v2,v3);	// Componentwise min/max
	v1 = sin(v2);		// Componentwise - all the usual functions...
	swap(v1,v2);		// In-place swap

	v3 = v1 DOT v2;		// Actually operator^
	v3 = v1 CROSS v2;	// Actually operator%

	float f = v1[0];	// Subscript
	float *fp = v1;		// Implicit conversion to float *

	f = len(v1);		// Length (also len2 == squared length)
	f = dist(p1, p2);	// Distance (also dist2 == squared distance)
	normalize(v1);		// Normalize (i.e., make it unit length)
				// normalize(vec(0,0,0)) => vec(1,0,0)
	v1 = trinorm(p1,p2,p3); // Normal of triangle

	cout << v1 << endl;	// iostream output in the form (1,2,3)
	cin >> v2;		// iostream input using the same syntax

Also defines the utility functions sqr, cube, sgn, fract, clamp, mix,
step, smoothstep, faceforward, reflect, and refract
*/


// Windows defines these as macros, which prevents us from using the
// type-safe versions from std::, as well as interfering with method defns
#undef min
#undef max


#include <cmath>
#include <iostream>
#include <algorithm>
using std::swap;
using std::sqrt;


// Let gcc optimize conditional branches a bit better...
#ifndef likely
#  if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
#    define likely(x) (x)
#    define unlikely(x) (x)
#  else
#    define likely(x)   (__builtin_expect((x), 1))
#    define unlikely(x) (__builtin_expect((x), 0))
#  endif
#endif


// Boost-like compile-time assertion checking
template <bool X> struct VEC_STATIC_ASSERTION_FAILURE;
template <> struct VEC_STATIC_ASSERTION_FAILURE<true>
	{ void operator () () {} };
#define VEC_STATIC_CHECK(expr) VEC_STATIC_ASSERTION_FAILURE<bool(expr)>()


template <int D, class T = float>
class Vec {
protected:
	T v[D];

public:
	// Constructor for no arguments.  Everything initialized to 0.
	Vec() { for (int i = 0; i < D; i++) v[i] = T(0); }

	// Uninitialized constructor - meant mostly for internal use
#define VEC_UNINITIALIZED ((void *) 0)
	Vec(void *) {}

	// Constructors for 2-4 arguments
	Vec(T x, T y)
		{ VEC_STATIC_CHECK(D == 2); v[0] = x; v[1] = y; }
	Vec(T x, T y, T z)
		{ VEC_STATIC_CHECK(D == 3); v[0] = x; v[1] = y; v[2] = z; }
	Vec(T x, T y, T z, T w)
		{ VEC_STATIC_CHECK(D == 4); v[0] = x; v[1] = y; v[2] = z; v[3] = w; }

	// Constructor from anything that can be accessed using []
	// Pretty aggressive, so marked as explicit.
	template <class S> explicit Vec(const S &x)
		{ for (int i = 0; i < D; i++) v[i] = T(x[i]); }

	// No destructor or assignment operator needed

	// Array reference and conversion to pointer - no bounds checking
	const T &operator [] (int i) const
		{ return v[i]; }
	T &operator [] (int i)
		{ return v[i]; }
	operator const T * () const
		{ return v; }
	operator const T * ()
		{ return v; }
	operator T * ()
		{ return v; }

	// Member operators
	Vec<D,T> &operator += (const Vec<D,T> &x)
	{
		for (int i = 0; i < D; i++)
#pragma omp atomic
			v[i] += x[i];
		return *this;
	}
	Vec<D,T> &operator -= (const Vec<D,T> &x)
	{
		for (int i = 0; i < D; i++)
#pragma omp atomic
			v[i] -= x[i];
		return *this;
	}
	Vec<D,T> &operator *= (const Vec<D,T> &x)
	{
		for (int i = 0; i < D; i++)
#pragma omp atomic
			v[i] *= x[i];
		return *this;
	}
	Vec<D,T> &operator *= (const T &x)
	{
		for (int i = 0; i < D; i++)
#pragma omp atomic
			v[i] *= x;
		return *this;
	}
	Vec<D,T> &operator /= (const Vec<D,T> &x)
	{
		for (int i = 0; i < D; i++)
#pragma omp atomic
			v[i] /= x[i];
		return *this;
	}
	Vec<D,T> &operator /= (const T &x)
	{
		for (int i = 0; i < D; i++)
#pragma omp atomic
			v[i] /= x;
		return *this;
	}

	// Set each component to min/max of this and the other vector
	Vec<D,T> &min(const Vec<D,T> &x)
	{
#pragma omp critical
		for (int i = 0; i < D; i++)
			if (x[i] < v[i]) v[i] = x[i];
		return *this;
	}
	Vec<D,T> &max(const Vec<D,T> &x)
	{
#pragma omp critical
		for (int i = 0; i < D; i++)
			if (x[i] > v[i]) v[i] = x[i];
		return *this;
	}

	// Outside of class: + - * / % ^ << >>

	// Some partial compatibility with valarrays and vectors
	typedef T value_type;
	size_t size() const
		{ return D; }
	T sum() const
		{ T total = v[0];
		  for (int i = 1; i < D; i++) total += v[i];
		  return total; }
	T avg() const
		{ return sum() / D; }
	T product() const
		{ T total = v[0];
		  for (int i = 1; i < D; i++) total *= v[i];
		  return total; }
	T min() const
		{ T m = v[0];
		  for (int i = 1; i < D; i++)
			if (v[i] < m)  m = v[i];
		  return m; }
	T max() const
		{ T m = v[0];
		  for (int i = 1; i < D; i++)
			if (v[i] > m)  m = v[i];
		  return m; }
	T *begin() { return &(v[0]); }
	const T *begin() const { return &(v[0]); }
	T *end() { return begin() + D; }
	const T *end() const { return begin() + D; }
	void clear() { for (int i = 0; i < D; i++) v[i] = T(0); }
	bool empty() const
		{ for (int i = 0; i < D; i++)
			if (v[i]) return false;
		  return true; }
	Vec<D,T> apply(T func(T)) const
		{ Vec<D,T> result(VEC_UNINITIALIZED);
		  for (int i = 0; i < D; i++) result[i] = func(v[i]);
		  return result; }
	Vec<D,T> apply(T func(const T&)) const
		{ Vec<D,T> result(VEC_UNINITIALIZED);
		  for (int i = 0; i < D; i++) result[i] = func(v[i]);
		  return result; }
};

typedef Vec<3,float> vec;
typedef Vec<3, float> point;
typedef Vec<2, float> point2;
typedef Vec<2, float> vec2;
typedef Vec<3,float> vec3;
typedef Vec<4,float> vec4;
typedef Vec<2,int> ivec2;
typedef Vec<3,int> ivec3;
typedef Vec<4,int> ivec4;


// Nonmember operators that take two Vecs
template <int D, class T>
static inline const Vec<D,T> operator + (const Vec<D,T> &v1, const Vec<D,T> &v2)
{
	Vec<D,T> result(VEC_UNINITIALIZED);
	for (int i = 0; i < D; i++)
		result[i] = v1[i] + v2[i];
	return result;
}

template <int D, class T>
static inline const Vec<D,T> operator - (const Vec<D,T> &v1, const Vec<D,T> &v2)
{
	Vec<D,T> result(VEC_UNINITIALIZED);
	for (int i = 0; i < D; i++)
		result[i] = v1[i] - v2[i];
	return result;
}

template <int D, class T>
static inline const Vec<D,T> operator * (const Vec&l
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值