【八叉树】从上千万个物体中【**瞬间**】就近选取坐标

众里寻他千百度,蓦然回首,那人却在灯火阑珊处

前情提要

在某些情况下,我们在场景中创建了数百万个物体,这些物体没有直接的网格或碰撞体(例如,通过GPU绘制的物体),因此无法通过常规的射线检测与碰撞体进行交互。我们仅掌握这些物体的坐标或顶点位置。在这种情况下,我们该如何通过鼠标来“选中”这些物体呢?

常规方式

1.创建鼠标到世界的射线

 Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
 Vector3 rayDirection = ray.direction;
 Vector3 rayOrigin = ray.origin;
 Vector3 rayEnd = rayOrigin + rayDirection * maxPickDistance;

2.遍历所有坐标点

①借用点积夹角计算筛选出与与射线方向一致

foreach (Vector3 point in points)
        {
            //点与射线夹角
            float dotAngle = Vector3.Dot(rayDirection, (point - rayOrigin).normalized);
            if (dotAngle > 0.99f)
            {
                float camDist = Vector3.Distance(rayOrigin, point);
                //点到射线距离
                var pointRayDist = SqDistPointSegment(rayOrigin, rayEnd, point);
                var normCamDist = (camDist / maxPickDistance) * pointRayDist * pointRayDist;

                if (normCamDist < nearestPointRayDist)
                {
                    if (pointRayDist > maxPickDistance) continue;
                    nearestPointRayDist = normCamDist;
                    nearestPoint = point;
                    isFindNearestPoint = true;
                }
            }
        }

②通过点积投影得到点到射线的距离

    public static float SqDistPointSegment(Vector3 start, Vector3 end, Vector3 point)
    {
        var ab = end - start;
        var ac = point - start;
        var bc = point - end;
        float e = Vector3.Dot(ac, ab);
        float f = Vector3.Dot(ab, ab);
        if (e >= f) return Vector3.Dot(bc, bc);
        return Vector3.Dot(ac, ac) - e * e / f;
    }

如此便可求得离射线最近坐标位置。

那么问题来了:当有上千万个点左边信息的时候,如此遍历一遍势必消耗大量的时间。下面我们将借助八叉树来优化该方案。

八叉树优化后的方案

1.创建八叉树

... 
Octree = new Octree(boundingBox, 500);//场景的范围Bounds和Octree迭代限制
//将所有点传入Octree初始化八叉树结构
        foreach (var point in pointCloudData)
        {
            Octree.Insert(point);
        }
... 

2.获取被射线穿过的Octree节点

    public List<Octree> GetNodesIntersectedByRay(Ray ray)
    {
        List<Octree> intersectedNodes = new List<Octree>();

        if (bounds.IntersectRay(ray))
        {
            intersectedNodes.Add(this);

            if (children != null)
            {
                foreach (var child in children)
                {
                    intersectedNodes.AddRange(child.GetNodesIntersectedByRay(ray));
                }
            }
        }

        return intersectedNodes;
    }

3.获取射线穿过Octree节点中的坐标数据

        var nodes = this._octree.GetNodesIntersectedByRay(ray);
        var points = new List<Vector3>();
         foreach (var node in nodes)
         {
             points.AddRange(node.points);
         }

4.通过常规方法遍历经过筛选后的Octree节点中的坐标数据

...
 foreach (Vector3 point in points)
        {
            float dotAngle = Vector3.Dot(rayDirection, (point - rayOrigin).normalized);
            if (dotAngle > 0.99f)
            {
...

经过八叉树优化后几乎可以做到实时选取

注意:可以调整八叉树的迭代分割限制条件来寻找更好的子节点Bounds范围,以此来加快最近点的玄策

posted @   世纪末の魔术师  阅读(312)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
  1. 1 ありがとう··· KOKIA
ありがとう··· - KOKIA
00:00 / 00:00
An audio error has occurred.

作词 : KOKIA

作曲 : KOKIA

编曲 : 日向敏文

作词 : KOKIA

作曲 : KOKIA

誰もが気付かぬうちに

誰もが気付かぬうちに

何かを失っている

フッと気付けばあなたはいない

思い出だけを残して

せわしい時の中

言葉を失った人形達のように

街角に溢れたノラネコのように

声にならない叫びが聞こえてくる

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

時には傷つけあっても

時には傷つけあっても

あなたを感じていたい

思い出はせめてもの慰め

いつまでもあなたはここにいる

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

もしも もう一度あなたに会えるなら

もしも もう一度あなたに会えるなら

たった一言伝えたい

もしも もう一度あなたに会えるなら

たった一言伝えたい

ありがとう

ありがとう

時には傷つけあっても

時には傷つけあっても

あなたを感じてたい

点击右上角即可分享
微信分享提示