当前位置: 首页 > news >正文

(转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库

1. 数学分析

1) 参数化直线

还记得我们在学习向量时介绍的位移向量吗?参数化直线的原理和他相同。其实所谓参数化直线,就是通过一个参数t来表示直线上的一个线段,当t为0时,则取线段的一端的端点,当t取1时,则取到另一端的端点。如图:

2011042515594696.png

核心原理就是向量加法的几何意义,Vd是加数,通过t(从0到1)去乘以这个Vd,则可以描述向量p,而p在某个t时的坐标值,就是从点P1到点P2的线段在t时的坐标值。注意这里我们使用的是图中的p = p1 + t * vd,这样t的取值是0到1,如果是p = p1 + t * v',那t的取值就是0到|Vd|,还需要去先算|Vd|,没有必要这么做,而且0和1是特殊的数字,运算方便。

所以参数化方程为:P(x,y,z) = p0 + v*t

我们为什么要用参数化的形式表示线段呢,因为在求两个线段的交点时,只要求两个参数方程组成的方程组,并求出t1和t2,只要他们都在0到1之间,那么就可以知道他们是有交点的。比如:

线段1:

x = 1 + 2*t1;

y = 3 + 4*t1;

线段2:

x = 5 + 6*t2;

y = 7 + 8*t2;

四个方程,四个未知数,可求得t1,t2,如果他们都在0和1之间,那他们相交。

具体怎么求?忘记上一章所讲的求逆矩阵的吗?要先算出行列式,然后。。。

注意,这里我们有很多的情况还没有考虑,比如:

1. 两条直线平行,没有交点

2. 两条线段在同一直线上,但互不重叠

3. 两条线段在同一直线上,部分重叠

4. 两条线段在同一直线上,只有一点重叠

在我的实现函数中,我把这4种特殊情况都看做是不相交或者说是无意义,当他们平行或共线时都会返回回传码0。因为这时计算出来的t1和t2以及交点都是没有任何意义的。

2) 3D平面

我们如何定义一个3D平面呢,看下图:

2011042516002578.png

如果你认真看图了,那么无需我多说了,只要有一个点P0,和平面的法线向量n,就可以确定一个平面了。

定义:对于空间中的任意一点P,如果线段P->P0与法线向量n垂直,则点P在平面上。

还记得向量点积吗?其最最重要的性质就是其几何意义:可以确定两个向量之间的夹角。当两个向量的点积为0时,这两个向量互相垂直。

所以:n.(P->P0) = 0

展开:<a, b, c> . <x-x0, y-y0, z-z0> = 0

最终,可以拿到平面的点-法线表示的形式:a*(x-x0) + b*(y-y0) + c(z-z0) = 0

我们可以另d = (-a*x0 - b*y0 - c*z0)

那么上面的形式可以化为:

a*x + b*y + c*z + d = 0

这个就是3D平面的通用形式,以通用形式计算两个平面之间的交线非常方便。

再扩展一下思维,就可以知道,对于任意一点<x,y,z>,a*(x-x0) + b*(y-y0) + c(z-z0)的值与0的关系,可以判断出,这个点在平面的哪一边。而这个性质则非常非常的重要,我打算先举个实例再给出答案。

还是参照上图,这是个特殊的平面,是X-Z平面,所以法线向量n为<0,1,0>,平面上的一点,我们选原点<0, 0, 0>,那么现在我们来取任意一点,比如(5, 5, 5),那么把这些数据代入a*(x-x0) + b*(y-y0) + c(z-z0),得:

1*(5-0) = 5 > 0,所以点(5, 5, 5)在平面的正方向。

再取点(5, -5, 5),则1*(-5-0) = -5 < 0 ,所以点(5, -5, 5)在该平面的负方向。

因为这个概念经常要用到,所以在举了一点的例子之后我想再通过理论证明一下,请看下图:

2011042516005574.png

这幅图是上图的平面版,其实任意一个3D点与平面的关系都可以找到这样一个角度来看。这里的X轴就是X-Z平面的水平视角中的线,Y轴的正半轴就是这个平面的法线向量,这个图左上提示了大家,u.v = |u| * |v| * Cos(theta)。左下角则根据向量范数的求法,给出了u.v是否大于0,取决于Cos(theta)的结果。图上4种颜色的点,标出了任意一点可能在平面附近的位置。还记得余弦函数线吗?不多说了。到此我们已经证明了在任意3D卦限的某点,与某3D平面,计算u.v > 0 则在平面正方向,u.v < 0 则在负方向。

3) 参数化直线与3D平面的交点

其实就是解方程组,把直线方程和3D平面方程放在一起进行简化。

参数化直线:

x = x0 + vx * t

y = y0 + vy * t

z = z0 + vz * t

其中x0, y0, z0为参数化直线上一直的一点

3D平面:

a*x + b*y + c*z + d = 0

a = nx

b = ny

c = nz

d = -nx*x0' -ny*y0' - nz*z0'

其中x0', y0', z0'是3D平面上已知的一点,所以加了'来区分直线上已知的一点

将这几个方程代入做整理变换可以得到t的表示形式:

t = -(a*x0 + b*y0 + c*z0 + d) / (a*vx + b*vy + c*vz)

通过这个t就可以知道线段与平面是否存在交点了,不用我再说了吧,看t是不是在0和1之间即可。

这几个方程也可以表示出x, y, z,即交点的坐标:

x = x0 + vx*t

y = y0 + vy*t

z = z0 + vz*t

哈哈,其实不就是把t代回原线段的形式了么。。。

2. 代码实现

1) 结构定义

首先我们定义表示参数化直线和3D平面的结构:

typedef struct PARAMLINE2D_TYPE // 参数化2D直线
{
	POINT2D p0;
	POINT2D p1;
	VECTOR2D v;
} PARAMLINE2D, *PARAMLINE2D_PTR;

typedef struct PARAMLINE3D_TYPE // 参数化3D直线
{
	POINT3D p0;
	POINT3D p1;
	VECTOR3D v;
} PARAMLINE3D, *PARAMLINE3D_PTR;

typedef struct PLANE3D_TYPE // 3D平面
{
	POINT3D p0;
	VECTOR3D n;
} PLANE3D, *PLANE3D_PTR;
2) 常用函数实现
void _CPPYIN_Math::ParamLineCreate(POINT2D_PTR p0, POINT2D_PTR p1, PARAMLINE2D_PTR p)
{
	p->p0.x = p0->x;
	p->p0.y = p0->y;
	p->p1.x = p1->x;
	p->p1.y = p1->y;
	p->v.x = p1->x - p0->x;
	p->v.y = p1->y - p0->y;
}

void _CPPYIN_Math::ParamLineCreate(POINT3D_PTR p0, POINT3D_PTR p1, PARAMLINE3D_PTR p)
{
	p->p0.x = p0->x;
	p->p0.y = p0->y;
	p->p0.z = p0->z;
	p->p1.x = p1->x;
	p->p1.y = p1->y;
	p->p1.z = p1->z;
	p->v.x = p1->x - p0->x;
	p->v.y = p1->y - p0->y;
	p->v.z = p1->z - p0->z;
}

void _CPPYIN_Math::ParamLineGetPoint(PARAMLINE2D_PTR p, double t, POINT2D_PTR pt)
{
	pt->x = p->p0.x + p->v.x * t;
	pt->y = p->p0.y + p->v.y * t;
}

void _CPPYIN_Math::ParamLineGetPoint(PARAMLINE3D_PTR p, double t, POINT3D_PTR pt)
{
	pt->x = p->p0.x + p->v.x * t;
	pt->y = p->p0.y + p->v.y * t;
	pt->z = p->p0.z + p->v.z * t;
}

int _CPPYIN_Math::ParamLineIntersect(PARAMLINE2D_PTR p1, PARAMLINE2D_PTR p2, double *t1, double *t2) // 0 平行或共线 1 线段相交 2 线段不相交
{
	double det_p1p2 = p1->v.x * p2->v.y - p1->v.y * p2->v.x;
	if (abs(det_p1p2) <= EPSILON)
		return 0;

	*t1 = (p2->v.x*(p1->p0.y - p2->p0.y) - p2->v.y*(p1->p0.x - p2->p0.x)) /det_p1p2;
	*t2 = (p1->v.x*(p1->p0.y - p2->p0.y) - p1->v.y*(p1->p0.x - p2->p0.x)) /det_p1p2;

	if ((*t1>=0) && (*t1<=1) && (*t2>=0) && (*t2<=1))
		return 1;
	else
		return 2;
}

int _CPPYIN_Math::ParamLineIntersect(PARAMLINE2D_PTR p1, PARAMLINE2D_PTR p2, POINT2D_PTR pt) // 0 平行或共线 1 线段相交 2 线段不相交
{
	double t1, t2, det_p1p2 = (p1->v.x*p2->v.y - p1->v.y*p2->v.x);

	if (abs(det_p1p2) <= EPSILON)
		return 0;

	t1 = (p2->v.x*(p1->p0.y - p2->p0.y) - p2->v.y*(p1->p0.x - p2->p0.x))/det_p1p2;
	t2 = (p1->v.x*(p1->p0.y - p2->p0.y) - p1->v.y*(p1->p0.x - p2->p0.x))/det_p1p2;

	pt->x = p1->p0.x + p1->v.x*t1;
	pt->y = p1->p0.y + p1->v.y*t1;

	if ((t1>=0) && (t1<=1) && (t2>=0) && (t2<=1))
		return 1;
	else
		return 2;
}

void _CPPYIN_Math::PlaneCreate(PLANE3D_PTR plane, POINT3D_PTR p0, VECTOR3D_PTR normal, int normalize)
{
	plane->p0.x = p0->x;
	plane->p0.y = p0->y;
	plane->p0.z = p0->z;

	if (normalize)
	{
		VECTOR3D n;
		VectorNormalize(normal, &n);
		plane->n.x = n.x;
		plane->n.y = n.y;
		plane->n.z = n.z;
	}
	else
	{
		plane->n.x = normal->x;
		plane->n.y = normal->y;
		plane->n.z = normal->z;
	}
}

double _CPPYIN_Math::PlaneWithPoint(POINT3D_PTR pt, PLANE3D_PTR plane)
{
	double hs = plane->n.x*(pt->x - plane->p0.x) + 
		plane->n.y*(pt->y - plane->p0.y) +
		plane->n.z*(pt->z - plane->p0.z);

	return hs;
}

int _CPPYIN_Math::PlaneParamLineInterset(PARAMLINE3D_PTR pline, PLANE3D_PTR plane, double *t, POINT3D_PTR pt) // 0 不相交 1 相交 2 相交在延长线上 3 线段在平面上
{
	// 求直线与平面法线向量的点积
	double plane_dot_line = VectorDot(&pline->v, &plane->n);

	if (abs(plane_dot_line) <= EPSILON) // 点积为0, 直线与面法线垂直,要么平行于平面,要不就在平面上,下面拿直线上一点测试
	{
		if (abs(PlaneWithPoint(&pline->p0, plane)) <= EPSILON)
			return 3;
		else
			return 0;
	}

	// 求t
	*t = -(plane->n.x*pline->p0.x + 
		   plane->n.y*pline->p0.y + 
		   plane->n.z*pline->p0.z -
		   plane->n.x*plane->p0.x - 
		   plane->n.y*plane->p0.y - 
		   plane->n.z*plane->p0.z) / (plane_dot_line);
   
	pt->x = pline->p0.x + pline->v.x*(*t);
	pt->y = pline->p0.y + pline->v.y*(*t);
	pt->z = pline->p0.z + pline->v.z*(*t);

	if (*t>=0.0 && *t<=1.0)
	   return 1;
	else
	   return 2;
}

3. 代码下载

完整项目代码下载:>>点击进入下载页<<

转自:http://blog.csdn.net/cppyin/archive/2011/02/10/6177213.aspx

转载于:https://www.cnblogs.com/CoolJie/archive/2011/03/03/1970227.html

相关文章:

  • 数据源绑定控件
  • 系统动力学软件vensim学习系列
  • 运用 GConf Cleaner 整顿 GConf
  • Qt/Embedded嵌入式开发环境的建立
  • 在嵌入式Linux情形下制造QPF字库的举措
  • Xvidcap:屏幕录像机
  • GrubED-Grub 编辑脚本
  • Google Earth 4.3 beta 界面字体增年夜术
  • Oracle根蒂根基知识
  • 使用SQL语句中between and查询数据出错
  • 网络编程[31]
  • 设置数据库兼容级别的两种方法
  • wordpress之模板汉化
  • 虚拟主机运行Asp.net MVC2程序体验(在不同版本的 IIS 上使用 ASP.NET MVC(转),付程序示例 )...
  • SQL Server 的最大容量规范
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • codis proxy处理流程
  • Django 博客开发教程 8 - 博客文章详情页
  • HTML-表单
  • mysql innodb 索引使用指南
  • overflow: hidden IE7无效
  • 创建一种深思熟虑的文化
  • 开发了一款写作软件(OSX,Windows),附带Electron开发指南
  • 前嗅ForeSpider中数据浏览界面介绍
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 微服务核心架构梳理
  • k8s使用glusterfs实现动态持久化存储
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • ​configparser --- 配置文件解析器​
  • ​VRRP 虚拟路由冗余协议(华为)
  • ​创新驱动,边缘计算领袖:亚马逊云科技海外服务器服务再进化
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (附源码)计算机毕业设计大学生兼职系统
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (循环依赖问题)学习spring的第九天
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • .Net 6.0 处理跨域的方式
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .net framework4与其client profile版本的区别
  • .NET Framework杂记
  • .NET Reactor简单使用教程
  • .NET 表达式计算:Expression Evaluator
  • .net 设置默认首页
  • .NET/C# 判断某个类是否是泛型类型或泛型接口的子类型
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • /var/log/cvslog 太大
  • [AndroidStudio]_[初级]_[修改虚拟设备镜像文件的存放位置]
  • [BT]BUUCTF刷题第4天(3.22)
  • [C++打怪升级]--学习总目录
  • [gdc19]《战神4》中的全局光照技术
  • [Git].gitignore失效的原因
  • [IE9] IE9 Beta崩溃问题解决方案