需求分析

原始需求

在过去的开发中,客户端和服务端都使用的是手动编辑逻辑包围盒,但这是一个费时费力的工作。随着技术发展,骨骼包围盒这种简化编辑流程,表现效果又好的方式应用开来,新的需求也就出来了。

游戏客户端为了表现效果好,使用的是骨骼包围盒,而且是一个持续区间,而服务端并不支持,服务端只支持瞬时的逻辑包围盒。那么服务端在需要做包围盒判定和校验的时候,需要重新再设置一套逻辑包围盒,与客户端形成一一对应。这样又回到了老路上,并不可取。

所以,由骨骼包围盒绘制逻辑包围盒的需求就出来了——将持续一段时间的骨骼包围盒扫过的空间转化为一个瞬时的逻辑包围盒

分析实现

在客户端,骨骼包围盒分三种——球体、胶囊体、立方体
在服务器,逻辑包围盒分多钟——立方体、圆柱、胶囊体、扇体、球体等

上图展示了其他类型包围盒的契合程度,很明显,带有旋转角度的立方体,也就是OBB是契合度最高的。OBB具有三个维度的长宽高和三个维度的旋转patch,yaw,roll,能够最大程度地还原骨骼包围盒范围,比其他类型的逻辑包围盒冗余是更小的。

那问题转换为如何将球体、胶囊体、立方体转换为立方体OBB,这里分2类讨论。

对球和胶囊体可以归为一类,先将其半径看成是零,算出立方体OBB后再对长宽高加上2倍半径值。那么原始数据就变成了1个点(球变为了点)的集合和2个点和1条线(胶囊体变成了线)的集合。
对立方体,原始数据是8个点和12条线的集合。

如果一个包围盒包含了所有的点,那么必然包含所有点之间的连线。这在数学上非常容易证明。

所以问题转换为点集求最小包围盒OBB的算法问题

执行流程

上图中蓝点为点集,红框为点集的OBB,青框为加上半径后的OBB

按照上述逻辑,流程总结为三步:

  1. 通过动态模拟骨骼包围盒在时间段内的运动轨迹,获取轨迹形成的点集和半径
  2. 依据点集,按照算法求出最小包围盒OBB
  3. 将OBB和半径转换为逻辑包围盒保存

第1步和第3部,逻辑比较简单,就不展开讲了,本文主要讲第2步的算法问题。

算法设计

注:下文所指,都是三维空间,虽然算法也适用于二维空间

本算法不是本人发明,而是查阅了资料。这只是一种拟合比较高的算法,并非绝对的最优解,而是相对最优解。何为相对最优解?就是你也许能找到更小的包围盒,但这个算法已经达到完全可用的程度。

算法整体思路是通过点集找一个方向,这个方向上点集的排布是最密集的
如何定义最密集呢?统计学上以方差作为评估标准,衍生出协方差和协方差矩阵
如何定义这个方向?这个方向也就是我们需要求的包围盒OBB的旋转

AABB和OBB

AABB(Axis-aligned bounding box)轴对齐包围盒,OBB(Oriented bounding box)有向包围盒。顾名思义,
AABB指垂直于坐标轴的包围盒,参数包含空间位置,长宽高
OBB指带有旋转的包围盒,参数包含空间位置,长宽高,旋转

根据定义,OBB就是AABB的旋转,那反过来,我们求OBB,也可以转换为将点集转换到AABB空间下,所得的AABB最小。

方差、协方差和协方差矩阵



特征值和特征向量

说了那么多,跟我们的协方差矩阵有什么关系呢?

协方差矩阵定义了我们数据的强度(方差)和方向(协方差)。因此,如果我们想用一个向量和它的大小来表示协方差矩阵,我们应该简单地尝试找到指向数据最大方向上的向量,其大小等于这个方向上的强度(方差)

按照上面的定义,我们就求得了特征值和特征向量。那么有什么用呢?
这里的特征向量就是我们求的线性变换矩阵

OBB的中心和长宽高

我们已经有了线性变换矩阵,将原始点转换到AABB空间,然后可以求出立方体OBB的中心点和长宽高了

上图中蓝点为原始轨迹点集,红点为蓝点经过线性变换矩阵转换后的AABB点集。

依据AABB点集,可以通过求三个轴的最大值和最小值,得到AABB中心点,AABB长宽高。
按照AABB与OBB的旋转,这里AABB长宽高就是OBB的长宽高,但AABB中心点并不是OBB的中心点。

为什么中心点不是同一个呢?
由于我们求的线性变换矩阵不包含位移变化,将蓝点转换的时候,同时将中心点也进行了转换。所以将AABB的中心点进行一次反转换,就得到OBB的中心点了。

Tip:你有没有发现红点所在AABB,Z轴大于Y轴,Y轴大于X轴。笔者知道跟特征向量的收敛有关,并没有证明为什么是这样。

变换矩阵转欧拉角

我们已经求得了OBB的中心点,长宽高和变换矩阵。那如何将变换矩阵转换到旋转的数据表示呢?

游戏中表示旋转的方式有很多种,一般有欧拉角、四元数、旋转轴等。但最常见的还是欧拉角。

欧拉角,不同于其他表示方式,有左手系与右手系的区别,还有顺规的区别。
我们定义旋转矩阵

定义每个轴的旋转(右手系)

如果以右手系,X-Y-Z顺规,我们能得到旋转矩阵

则可以如下表示欧拉角(不考虑万向节锁):

然后按照公式导出欧拉角就可以了

尴尬的反射矩阵

测试上述逻辑后,发现算法有50%是正确的,有50%出现下面的情况

上图中红色是算法的结果,青色是正确的结果

通过查看空间结构,发现欧拉角的z轴旋转roll出现了正负号的问题,也就是roll轴的旋转有时候需要负号,有时候不需要。原因是什么呢?
查阅了大量资料,并没有结果,一度怀疑欧拉角计算出了问题。

一个无意间,计算det(rotate_matrix)(旋转矩阵的行列式)等于-1,不对呀,旋转矩阵行列式为+1啊。
原因找到啦!这个线性变换矩阵有旋转矩阵和反射矩阵。

线性变换矩阵,包含三种变换——位移变换、旋转变换和反射变换
如果行列式是1则是旋转矩阵,如果行列式是-1则是反射矩阵(可能包含旋转)

实对称矩阵的特征向量存在正交矩阵,正交矩阵的行列式为±1。
而协方差矩阵就是实对称矩阵,所以我们求得的线性变换矩阵包含了旋转变换和反射变换

我们根据线性变换矩阵行列式是否是-1来设置roll的旋转角是否添加负号。

按照算出的欧拉角,就获得可如下的结果

上图中蓝点是原始变换矩阵结果,紫红点是欧拉角转换的结果

图中的欧拉角结果与原始结果并不一致,原因是原始变换矩阵带有一个Z轴的反射,而欧拉角旋转矩阵没有。
也就是说,其构造的欧拉角旋转矩阵并非是点集的正确转换,只是OBB的正确表示方式。这不影响我们使用其OBB作为逻辑包围盒。

TIP:这里引申出一个问题,既然协方差矩阵求特征向量的时候,可以带反射,也可以不带,只是方向不同。那么是否存在不带反射的特征向量的解,笔者线代水平有限就不深入探讨了。

实现代码

import mathfrom numpy import ndarray, dot, cov, transpose, array, min as npmin, max as npmax
from numpy.linalg import eigh, detclass OBB:def __init__(self):self._rotate_matrix = None  # 线性变换矩阵self._center = None  # 中心点self._extents = None  # 宽高长def transform(self, point):  # 点转换到目标target = dot(array(point) - self._center, self._rotate_matrix) + self._centerreturn self._to_float_list(target)def transform_to_origin(self, point):  # 点转换到原始target = dot(array(point) - self._center, transpose(self._rotate_matrix))return self._to_float_list(target)def _to_float_list(self, obj_array):return [float(x) for x in obj_array]@propertydef center(self):return self._to_float_list(self._center)@propertydef extents(self):return self._to_float_list(self._extents)@propertydef pitch_yaw_roll(self):R = self._rotate_matrix# 这里是左手系Y-X-Z顺规,其他方式欧拉角请自己计算sy = math.sqrt(R[2][0] * R[2][0] + R[2][2] * R[2][2])pitch = math.atan2(-R[2][1], sy)yaw = math.atan2(R[2][0], R[2][2]) if sy > 1e-6 else math.atan2(-R[0][2], R[0][0])roll = math.atan2(R[0][1], R[1][1]) if sy > 1e-6 else 0if det(self._rotate_matrix) < 0:  # 针对反射矩阵进行特判roll = -rollreturn (pitch, yaw, roll)@classmethoddef build_from_points(cls, points):if not isinstance(points, ndarray):points = array(points, dtype=float)assert points.shape[1] == 3, 'points have to have 3-elements'covariance_matrix = cov(points, y=None, rowvar=0, bias=1)  # 计算协方差矩阵_, eigen_vectors = eigh(covariance_matrix)  # 计算协方差矩阵的特征值和特征矩阵trans_matrix = eigen_vectorsrotate_matrix = transpose(trans_matrix)# 由原始点转换到AABB,求中心和长宽高p_primes = array([dot(p, trans_matrix) for p in points])min_p = npmin(p_primes, axis=0)max_p = npmax(p_primes, axis=0)center = dot((min_p + max_p) / 2.0, rotate_matrix)extents = (max_p - min_p)obb = OBB()obb._rotate_matrix = rotate_matrixobb._center = centerobb._extents = extentsreturn obb

点集求最小包围盒OBB算法相关推荐

  1. PCL ——最小包围盒(画出了最小包围盒并求出顶点坐标)

    PCL --最小包围盒 2018年09月21日 15:31:01 不懂音乐的欣赏者 阅读数:35 标签: PCL包围盒外接矩形最小矩形收起 个人分类: PCL 1.包围盒简介   包围盒也叫外接最小矩 ...

  2. 利用Kuhn-Munkras算法求最小权值匹配

    本文参考博客: http://blog.csdn.net/zhangpinghao/article/details/12242823(代码参考该博客) http://philoscience.itey ...

  3. cad生成最小包围盒lisp_cad生成最小包围盒lisp_点集的最小包围圆(用LISP求解)

    ;;;************************************ ;;;求最小包围圆的lisp程序-------------- ;;;其算法为参见了有关文献-------------- ...

  4. SPH算法(求最小代价树)

    一.sph算法简介 1.最小代价树算法 SPH算法也叫做MPH( minimum path heuristic)算法, 用于构造时延约束最算法小代价组播树. 该算法中每 个目的结点通过与当前组播树有最 ...

  5. 采样点 求拐点 算法 c语言,平面曲线离散点集拐点的快速查找算法.pdf

    平面曲线离散点集拐点的快速查找算法 第 25 卷 第 6 期 北 方 交 通 大 学 学 报 Vol . 25 No . 6 200 1 年 12 月 J OU RNAL OF NOR THERN J ...

  6. 辗转取余数php算最小公约数,(算法)求最大公约数和最小公约数

    最大公约数定义: 最大公约数(最大公因数)就是几个数公有的因数中最大的一个. 最小公倍数定于: 最小公倍数就是几个数公有的倍数中最小的一个. 求最小公倍数的算法:(两个数的乘积/最大公约数) 求最大公 ...

  7. 算法训练 - 调和数列问题 输入一个实数x,求最小的n使得,1/2+1/3+1/4+...+1/(n+1)>=x。   输入的实数x保证大于等于0.01,小于等于5.20,并且恰好有两位小数。你的

    问题描述 输入一个实数x,求最小的n使得,1/2+1/3+1/4+-+1/(n+1)>=x. 输入的实数x保证大于等于0.01,小于等于5.20,并且恰好有两位小数.你的程序要能够处理多组数据, ...

  8. 连通域最小外接矩形算法原理_算法|图论 2W字知识点整理(超全面)

    作者:SovietPower✨ 链接:https://ac.nowcoder.com/discuss/186584 来源:牛客网 度数序列 对于无向图, 为每个点的度数.有 (每条边被计算两次).有偶 ...

  9. 离散实验一 油管铺设 (求最小生成树的Prim算法的实际应用)

    实验一 油管铺设 2018.11.06于CSDN释放 详见GitHub: https://github.com/DolorHunter/PrimAlgorithm 实验准备 最小生成树问题,求最小生成 ...

  10. 【H5/JS】游戏常用算法-碰撞检测-包围盒检测算法(2)-矩形

    矩形包围盒,顾名思义,就是使用一个矩形来包围住图像,矩形的大小以刚好包围住图像为最佳,这种包围盒最适用的场景是刚好物体的形状接近于矩形. 在具体的应用中,描述矩形包围盒的的常用方式有以下两种, 一:采 ...

最新文章

  1. 计算机网络 物理层链路层
  2. cJSON使用教程(树外构建 out of tree build 概念)(组包概念)
  3. P3272 [SCOI2011]地板(插头DP)
  4. 怎么利用计算机计算潮流计算,电力系统潮流计算的目的是什么
  5. 调用百度 Echarts 显示重庆市地图
  6. 剑指 Offer 06. 从尾到头打印链表(递归、逆置链表、头部动态插入)
  7. 深度学习(三十三)CRF as RNN语义分割-未完待续
  8. javascript实现简体与繁体的转换(可下载)
  9. 《C#图解教程》读书笔记之五:委托和事件
  10. linux准备环境,组建自己的Linux-01准备环境
  11. hub设备_五年内任何问题,直接换新,毕亚兹 USB3.0四口HUB分线器体验
  12. IT小知识:文件扩展名小知识大全(A~Z)文件扩展名大全
  13. 阿阳网客:steam搬砖项目超完整版实操分享
  14. Mac Mini换固态硬盘
  15. 【光学】基于矩阵法和等效界面法分析光学薄膜膜对反射率影响附matlab代码
  16. python空气质量提醒代码_【python】全国空气质量分析系统
  17. [转]的C#实现三维数字地形漫游(基于Irrlicht)
  18. 如何有效提高公众号文章阅读量
  19. 网络推广优化专员工作职责,网络推广专员工作内容
  20. 虚拟机安装linux

热门文章

  1. 西门子ug通用后处理_浅谈SIEMENS数控系统UG后置处理常用TCL语句使用技巧
  2. 2020洪灾地图_卫星地图看洪灾:“告急”的鄱阳湖发生了什么?
  3. 时间序列之向量自回归检验VAR(自相关性)(2)
  4. 数据分析师出品:人力资源岗位年终总结可视化模板
  5. linux bzip指定名称,bzip2命令_Linux bzip2 命令用法详解:将文件压缩成bz2格式
  6. 关于Steam服务器登录的一些问题的解决方案
  7. steam网络相关问题-社区错误代码118/无法自动登陆/短期内来自您网络的失败登录过多/无法连接至steam网络(2021/2/18更新)
  8. 输出100以内的素数(质数)
  9. STM32CubeMX系列TIM
  10. class文件反编译后的汉字乱码问题