如果你的粒子是统一大小的,而你的三角形不是,那往往需要一种非常不同的数据结构。对于大小均匀的粒子,您可以将它们视为点,并将一个粒子存储在树的一个叶节点中作为空间中的一个点(只要所有粒子都具有大的半径/大小,这无关紧要)大小一致,因为您的搜索查询(只要它们按粒子大小扩展)将始终选取它们的中心点)。
对于三角形,它们的大小可以变化很大,并且可以与 8 个子八分圆中的多个相交。因此,您可以做几件我知道的事情:
- 只需在具有一些冗余的多个叶子中插入指向三角形的指针/索引,或者简单地复制所有叶子中的整个三角形数据(如果您在调整根据内容设置树的参数,但它可以使搜索对缓存更加友好)。
- 拆分三角形,这样如果它与两个或更多八分圆相交,就变成两个或更多拆分三角形。这对于构建/更新来说往往有点昂贵,但如果您以缓存友好的方式将三角形数据直接存储在叶子中,则查询速度可能会很快。
- 使用松散的表示,例如松散的八叉树。在这种情况下,您只需在插入时将三角形视为单个点。但是,您在插入过程中扩展了您遍历的所有八叉树节点的 AABB 以包含三角形。
#1 往往适合动态内容,只要您尽量减少构建树所涉及的内存使用和处理,并设置一些合理的限制,例如八叉树的最大深度,以便它不想无限期地分裂。
#2 往往非常适合静态内容,并且往往非常适合快速搜索,因为它可以生成比 #1 更浅且更平衡的树,因为您没有将重叠数据存储在叶子倾向于希望它们分裂得比理想情况下更多。如果您的数据是静态的,那么您可能只需要直接在八叉树中存储一个副本,八叉树可以随意修改该数据以提供最快的搜索。
#3 往往非常适合动态内容,因为它很好地平衡了构建/更新树和搜索树之间的开销。然而,松散的八叉树对搜索查询的性能造成了很大的打击,因为以前对中心点进行简单检查以确定要遍历哪些八分圆现在需要检查所有 8 个八分圆的 AABB 以确定哪些子在搜索过程中遍历。但是,它显着减少了构建和更新树的开销,使其在动态内容中得到了很好的平衡,例如,网格在交互式实时上下文中对每一帧进行变形。
它有助于解决这些类型的问题,以准确指定您的需求,例如您的网格是否是静态的,您是否需要快速更新/构建/搜索或平衡(您通常无法将这三件事都做到极致快:使一个超快通常意味着使另一个变慢)。
例如,光线追踪器经常使用非常适合极其静态内容的代表,因为他们通常可以花一些额外的时间来构建结构,因为他们之后可能会针对它执行十亿次光线/三角形相交测试。光线追踪器花费一百毫秒来构建/更新其空间索引并不一定是什么大问题,因为它可能需要几秒钟甚至几分钟或几小时才能以生产质量渲染整个场景。按照离线渲染标准,100 毫秒是微不足道的时间,按照实时标准,这是一个史诗般的时间。另一方面,最快的搜索时间对于光线追踪器来说非常重要,所以松散的八叉树通常是一个可怕的选择,并且大多数光线追踪器通常希望将进行交叉测试所需的数据深度复制到叶子中,这样他们就不会'不需要零星的内存访问模式来访问存储在叶子中的数据。
但是,处理实时环境中移动的许多事物的碰撞检测的物理引擎需要一种非常不同的表示,适合动态内容。在完全交互式的实时上下文中处理大量静态和动态内容的游戏引擎可能会使用多种数据结构:每种数据结构都针对它们存储的内容类型量身定制。
如果您的粒子确实是统一大小的,那么我建议使用不同的数据结构(例如,不同类型的八叉树)来存储三角形网格。如果您的粒子非常动态,而您的网格相当静态,则情况相同。在这种情况下,如果您正在搜索查看粒子是否与网格或粒子碰撞,您可能首先检查存储针对三角形优化的网格的八叉树。如果没有找到相交的三角形,则搜索适合存储动态、大小均匀的粒子的另一个八叉树,以检查粒子与粒子之间的碰撞。您仍然可以将其抽象到看起来像单个数据结构的程度,但理想情况下您应该为此使用两种不同的实现。
对于转换的网格,存储两个八叉树也很有用:整个网格的外部八叉树仅用于针对其 AABB 的粗略交叉测试,例如然后,如果粗相交测试通过,则搜索每个网格存储的八叉树以查找哪个三角形相交。这使您可以避免在网格变换时(例如:当它们旋转时)更新存储三角形的八叉树。