一种基于体素的射线检测

news2025/1/12 22:53:57

效果

基于体素的射线检测

一个漏检的射线检测

从起点一直递增指定步长即可得到一个稀疏的检测

    bool Raycast(Vector3 from, Vector3 forword, float maxDistance)
    {
        int loop = 6666;
        Vector3 pos = from;
        Debug.DrawLine(from, from + forword * maxDistance, Color.red);
        while (loop-- > 0)
        {
            pos += forword;
            if((pos - from).magnitude > maxDistance)
            {
                break;
            }
            Vector3Int blockPosition = Vector3Int.RoundToInt(pos);
            if (world.HasBlockCollider(blockPosition))
            {
                return true;
            }
            if(world.HasVoxelCollider(blockPosition))
            {
                return true;
            }

            Gizmos.DrawWireCube(blockPosition,Vector3.one);

        }
        return false;
    }

在这里插入图片描述
可以看到上图有很多地方因为迭代的步长过大导致漏检
为了补充这些空洞可以使用Bresenham重新修改算法

填补空缺

修改步长会导致迭代次数暴增,并且想要不漏检需要很小的步长。下面使用了检测相交点是否连续检测是否空缺
首先射线经过的点必然连续,那么可以我们就可以直接对比上一次离开方块时的点和当前进入方块的点

	leavePoint = GetIntersectPoint(aabb, leaveRay, leavePoint);
    static Vector3 GetIntersectPoint(Bounds aabb, Ray ray, Vector3 point)
    {
        if (aabb.IntersectRay(ray, out var distance))
        {
            point = ray.GetPoint(distance);
        }
        else // 由于射线平行于方块的面或边导致没有相交,稍微放大方块强行相交
        {
            aabb.size *= 1.01f;
            if (aabb.IntersectRay(ray, out distance))
            {
                point = ray.GetPoint(distance);
            }
        }
        return point;
    }

如果2个坐标是相等的。可以认为射线并没有漏检

oldPoint = posInt;
aabb.center = posInt;
aabb.size = Vector3.one;
if (aabb.IntersectRay(enterRay, out distance))
{
    enterPoint = enterRay.GetPoint(distance);
    if (leavePoint != enterPoint)
    {
        //存在漏检
    }
}

否则就需要补充漏检的方块,有可能射线一次漏了2个方块没有检测
在这里插入图片描述
先检测最靠近离开位置的坐标是否有方块

distance = (enterPoint - leavePoint).magnitude * 0.01f;
fillPoint = Vector3Int.RoundToInt(leavePoint + forward * distance);
if (checkCollider(fillPoint, ref hitInfo))
    return true;

再检测靠近进入位置的坐标是否有方块

fillPoint2 = Vector3Int.RoundToInt(enterPoint - forward * distance);
if (fillPoint2 != fillPoint)
{
    if (checkCollider(fillPoint2, ref hitInfo))
        return true;
}

手动覆盖漏检的方块,青色为补充的检测
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
多个轴向观察射线是否在绘制的方块内

细分方块

把一个方块切成 444 共计64个方块
那么它们的相对索引就是
世界坐标转为体素内部坐标

    public Vector3 PositionToVoxelPosition(Vector3 position)
    {
        var pos = Vector3Int.RoundToInt(position);
        position -= voxelOffset;
        position -= pos;
        position *= voxelScale;
        position += Vector3Int.one;
        return Vector3Int.RoundToInt(position);
    }

切分时使用ulong存储体素信息。如果某一位是1,即当前位置拥有体素
方块内部坐标转索引。使用索引检测当前位是否有体素

    public int VoxelPositionToIndex(Vector3 position)
    {
        return (int)Mathf.Abs(position.x * BlockWorld.planeCount + position.y * BlockWorld.voxelScale + position.z);
    }

体素检测,先检测当前位置是否是体素块,如果是,检测方块体内该位置是否有体素

    public bool HasVoxelCollider(Vector3 position, out Vector3 result)
    {
        if (voxelDict.TryGetValue(Vector3Int.RoundToInt(position), out ulong value))
        {
            result = PositionToVoxelPosition(position);
            int index = VoxelPositionToIndex(result);
            if ((value >> index & 1) == 1)
            {
                result = VoxelPositionToWorldPosition(position, result);
                return true;
            }
            result = Vector3.zero;
            return false;
        }
        result = position;
        return false;
    }

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

完整代码

using System;
using System.Collections.Generic;
using UnityEngine;

public class BlockWorld:IDisposable
{
    public const int voxelScale = 4;
    public const int planeCount = voxelScale * voxelScale;
    public static Vector3 voxelSize = Vector3.one / voxelScale;
    public static Vector3 voxelStartOffset = voxelSize * 0.5f;
    public static Vector3 voxelAABBSize = Vector3.one + BlockWorld.voxelSize * 2;
    public static Vector3 voxelOffset = Vector3.one * 0.5f + voxelStartOffset;
    private readonly Dictionary<Vector3Int, bool> blocks = new Dictionary<Vector3Int, bool>();
    private readonly Dictionary<Vector3Int, ulong> voxelDict = new Dictionary<Vector3Int, ulong>();
    public void AddBlock(Vector3Int position)
    {
        blocks[position] = true;
    }

    public void AddVoxel(Vector3Int blockPosition, ulong value)
    {
        voxelDict[blockPosition] = value;
    }

    public void AddVoxel( Vector3 voxelPosition)
    {
        var blockPosition = Vector3Int.RoundToInt(voxelPosition);
        voxelDict.TryGetValue(blockPosition, out ulong value);
        voxelPosition = PositionToVoxelPosition(voxelPosition);
        int index = VoxelPositionToIndex(voxelPosition);
        value |= (ulong)1 << index;
        voxelDict[blockPosition] = value;
    }

    public Vector3 PositionToVoxelPosition(Vector3 position)
    {
        var pos = Vector3Int.RoundToInt(position);
        position -= voxelOffset;
        position -= pos;
        position *= voxelScale;
        position += Vector3Int.one;
        return Vector3Int.RoundToInt(position);
    }

    public Vector3 VoxelPositionToWorldPosition(Vector3 position, Vector3 voxelPosition)
    {
        return voxelPosition / BlockWorld.voxelScale + BlockWorld.voxelSize + BlockWorld.voxelStartOffset + Vector3Int.RoundToInt(position);
    }

    public int VoxelPositionToIndex(Vector3 position)
    {
        return (int)Mathf.Abs(position.x * BlockWorld.planeCount + position.y * BlockWorld.voxelScale + position.z);
    }

    public void Clear()
    {
        blocks.Clear();
        voxelDict.Clear();
    }

    public bool HasBlockCollider(Vector3Int position)
    {
        return blocks.ContainsKey(position);
    }

    public bool HasVoxelCollider(Vector3Int position)
    {
        return voxelDict.ContainsKey(position);
    }

    public bool HasVoxelCollider(Vector3 position, out Vector3 result)
    {
        if (voxelDict.TryGetValue(Vector3Int.RoundToInt(position), out ulong value))
        {
            result = PositionToVoxelPosition(position);
            int index = VoxelPositionToIndex(result);
            if( (value >> index & 1) == 1)
            {
                result = VoxelPositionToWorldPosition(position, result);
                return true;
            }
            result = Vector3.zero;
            return false;
        }
        result = position;
        return false;
    }

    public ulong GetVoxelValue(Vector3Int position)
    {
        voxelDict.TryGetValue(position, out var value);
        return value;
    }


    void IDisposable.Dispose()
    {
        Clear();
    }
}

using UnityEngine;

public static class BlockPhysics
{
    private const int MAX_LOOP_COUNT = 6666;
    public static bool Raycast(BlockWorld world, Vector3 from, Vector3 forward, float maxDistance, out RaycastHit hitInfo, bool isDraw = false)
    {
#if !UNITY_EDITOR
        isDraw = false;
#endif

        float distance;
        int loop = MAX_LOOP_COUNT;
        Vector3 to = from + forward * maxDistance;
        Vector3 pos = from;
        Vector3 tForward = forward * 0.9f;
        Vector3Int posInt = Vector3Int.RoundToInt(pos);
        Vector3Int oldPoint = posInt;
        Vector3Int fillPoint;
        Vector3Int fillPoint2;
        Vector3 leavePoint = from;
        Vector3 enterPoint = default;

        Bounds aabb = default;
        Ray enterRay = default;
        Ray leaveRay = default;

        enterRay.origin = from;
        enterRay.direction = forward;
        leaveRay.origin = to + forward * 2;
        leaveRay.direction = -forward;
        hitInfo = default;
        aabb.center = posInt;
        aabb.size = Vector3.one;

        if (aabb.IntersectRay(leaveRay, out distance))
        {
            leavePoint = leaveRay.GetPoint(distance);
        }

        if (maxDistance - (int)maxDistance > 0)
        {
            maxDistance += forward.magnitude * 0.9f;
        }

#if UNITY_EDITOR
        int index = 0;
        if (isDraw)
        {
            Debug.DrawLine(from, to, Color.red);
        }
#endif

        while (loop-- > 0)
        {
            pos += tForward;

            if ((pos - from).magnitude > maxDistance)
            {
                break;
            }

            posInt = Vector3Int.RoundToInt(pos);
            if (posInt == oldPoint)
                continue;

            oldPoint = posInt;
            aabb.center = posInt;
            aabb.size = Vector3.one;
            if (aabb.IntersectRay(enterRay, out distance))
            {
                enterPoint = enterRay.GetPoint(distance);
                if (leavePoint != enterPoint)
                {
                    distance = (enterPoint - leavePoint).magnitude * 0.01f;
                    fillPoint = Vector3Int.RoundToInt(leavePoint + forward * distance);
                    if (checkCollider(fillPoint, ref hitInfo))
                        return true;

                    fillPoint2 = Vector3Int.RoundToInt(enterPoint - forward * distance);
                    if (fillPoint2 != fillPoint)
                    {
                        if (checkCollider(fillPoint2, ref hitInfo))
                            return true;
                    }
                }
            }

            if (checkCollider(posInt, ref hitInfo))
                return true;

            leavePoint = GetIntersectPoint(aabb, leaveRay, leavePoint);
        }

        return false;

        bool checkCollider(Vector3Int origin, ref RaycastHit hitInfo)
        {
#if UNITY_EDITOR
            if (isDraw)
            {
                Gizmos.color = Color.grey;
                Gizmos.DrawWireCube(origin, Vector3.one);
                UnityEditor.Handles.Label(origin, $"[{index++}]");
            }
#endif
            if (world.HasBlockCollider(origin))
            {
                aabb.center = origin;
                aabb.size = Vector3.one;
                hitInfo.point = origin;
                if (aabb.IntersectRay(enterRay, out distance))
                {
                    hitInfo.point = enterRay.GetPoint(distance);
#if UNITY_EDITOR
                    if (isDraw)
                    {
                        Gizmos.color = Color.red;
                        Gizmos.DrawWireCube(origin, Vector3.one);
                        UnityEditor.Handles.Label(hitInfo.point, $"【{hitInfo.point.x}, {hitInfo.point.y}, {hitInfo.point.z}】");
                    }
#endif
                }
                return true;
            }

            if (world.HasVoxelCollider(origin))
            {
                if (RaycastVoxel(world, from, forward, origin, maxDistance, out hitInfo, isDraw))
                {
                    return true;
                }
            }
            return false;
        }
    }

    static bool RaycastVoxel(BlockWorld world, Vector3 from, Vector3 forward, Vector3 blockPosition, float maxDistance, out RaycastHit hitInfo, bool isDraw = false)
    {
        hitInfo = default;

        float distance = 0f;
        int loop = MAX_LOOP_COUNT;
        Vector3 pos = from;
        Vector3 tForward = forward * 0.24f;
        Vector3 voxelPosition;
        Vector3 leavePoint = from;
        Vector3 result = default;
        Vector3 fillPoint = default;
        Vector3 fillPoint2 = default;
        Vector3 enterPoint = default;
        Bounds aabb = default;

        Ray enterRay = default;
        enterRay.origin = from;
        enterRay.direction = forward;

        Ray leaveRay = default;
        leaveRay.origin = (from + forward * maxDistance) + forward * 2;
        leaveRay.direction = -forward;

        aabb.center = blockPosition;
        aabb.size = Vector3.one;
        if (aabb.IntersectRay(enterRay, out distance))
        {
            enterPoint = enterRay.GetPoint(distance);
            pos = enterPoint;
            leavePoint = enterPoint;
        }

#if UNITY_EDITOR
        if (isDraw)
        {
            Gizmos.DrawWireSphere(enterPoint, 0.05f);
        }
        int index = 0;
#endif
        while (loop-- > 0)
        {
            pos += tForward;
            if ((pos - from).magnitude > maxDistance)
            {
                break;
            }

            aabb.center = blockPosition;
            aabb.size = BlockWorld.voxelAABBSize;


            if (!aabb.Contains(pos))
                break;

            voxelPosition = world.PositionToVoxelPosition(pos);
            voxelPosition = world.VoxelPositionToWorldPosition(pos, voxelPosition);

            aabb.center = voxelPosition;
            aabb.size = BlockWorld.voxelSize;
            if (aabb.IntersectRay(enterRay, out distance))
            {
                enterPoint = enterRay.GetPoint(distance);
                if (leavePoint != enterPoint)
                {
                    distance = (enterPoint - leavePoint).magnitude * 0.01f;
                    fillPoint = leavePoint + forward * distance;

                    if (checkCollider(fillPoint, ref hitInfo))
                    {
                        return true;
                    }

                    fillPoint2 = enterPoint - forward * distance;
                    if (world.PositionToVoxelPosition(fillPoint) != world.PositionToVoxelPosition(fillPoint2))
                    {
                        if (checkCollider(fillPoint2, ref hitInfo))
                            return true;
                    }
                }
            }

            if (checkCollider(pos, ref hitInfo))
            {
                return true;
            }

            leavePoint = GetIntersectPoint(aabb, leaveRay, leavePoint);
        }

        return false;

        bool checkCollider(Vector3 origin, ref RaycastHit hitInfo)
        {
#if UNITY_EDITOR
            if (isDraw)
            {
                Gizmos.color = Color.gray;
                var voxelPoint = world.PositionToVoxelPosition(origin);
                voxelPoint = world.VoxelPositionToWorldPosition(origin, voxelPoint);
                Gizmos.DrawWireCube(voxelPoint, BlockWorld.voxelSize);
                UnityEditor.Handles.Label(voxelPoint, $"[{index++}]");
            }
#endif
            if (world.HasVoxelCollider(origin, out result))
            {
                aabb.center = result;
                aabb.size = BlockWorld.voxelSize;
                hitInfo.point = result;
                if (aabb.IntersectRay(enterRay, out distance))
                {
                    hitInfo.point = enterRay.GetPoint(distance);
#if UNITY_EDITOR
                    if (isDraw)
                    {
                        Gizmos.color = Color.red;
                        var voxelPoint = world.PositionToVoxelPosition(origin);
                        voxelPoint = world.VoxelPositionToWorldPosition(origin, voxelPoint);
                        Gizmos.DrawWireCube(voxelPoint, BlockWorld.voxelSize);
                        UnityEditor.Handles.Label(hitInfo.point, $"【{hitInfo.point.x}, {hitInfo.point.y}, {hitInfo.point.z}】");
                    }
#endif
                }
                return true;
            }
            return false;
        }
    }

    static Vector3 GetIntersectPoint(Bounds aabb, Ray ray, Vector3 point)
    {
        if (aabb.IntersectRay(ray, out var distance))
        {
            point = ray.GetPoint(distance);
        }
        else
        {
            aabb.size *= 1.01f;
            if (aabb.IntersectRay(ray, out distance))
            {
                point = ray.GetPoint(distance);
            }
        }
        return point;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1056776.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3切换路由白屏刷新后才显示页面内容

问题所在&#xff1a; 1.首先检查页面路由以及页面路径配置是否配置错误。 2.如果页面刷新可以出来那证明不是配置的问题&#xff0c;其次检查是否在根组件标签最外层包含了个最大的div盒子包裹内容。&#xff08;一般vue3是可以不使用div盒子包裹的&#xff09; 3.最后如果…

快速开发微信小程序之一登录认证

一、背景 记得11、12年的时候大家一窝蜂的开始做客户端Android、IOS开发&#xff0c;我是14年才开始做Andoird开发&#xff0c;干了两年多&#xff0c;然后18年左右微信小程序火了&#xff0c;我也做了两个小程序&#xff0c;一个是将原有牛奶公众号的功能迁移到小程序&#x…

【iptables 实战】04 高级用法:iptables模块之state扩展

一、场景描述 假设我们内部的主机&#xff0c;想访问新闻网站&#xff0c;即访问其80端口。要想正确地获取到http报文内容。我们对这个网络报文应该这么设置 出去的报文&#xff0c;目标端口&#xff0c;80端口&#xff0c;放行进入的报文&#xff0c;源端口&#xff0c;80端…

vertx的学习总结2

一、什么是verticle verticle是vertx的基本单元&#xff0c;其作用就是封装用于处理事件的技术功能单元 &#xff08;如果不能理解&#xff0c;到后面的实战就可以理解了&#xff09; 二、写一个verticle 1. 引入依赖&#xff08;这里用的是gradle&#xff0c;不会吧&#…

几个推荐程序员养成的好习惯

本文框架 前言case1 不想当然case2 不为了解决问题而解决问题case3 不留问题死角case4 重视测试环节 前言 中秋国庆双节至&#xff0c;旅行or回乡探亲基本是大家的选择&#xff0c;看看风景或陪陪家人确实是个难得的机会。不过我的这次假期选择了闭关&#xff0c;不探亲&#…

手机号码(作为查询参数)格式校验:@PhoneQuery(自定义参数校验注解)

目标 自定义一个用于校验&#xff08;作为查询参数的&#xff09;手机号码格式的注解PhoneQuery&#xff0c;能够和现有的 Validation 兼容&#xff0c;使用方式和其他校验注解保持一致。 校验逻辑 可以为 null 或 空字符串&#xff1b;不能包含空格&#xff1b;必须为数字序…

开放式耳机哪个好、百元开放式耳机推荐品牌

开放式耳机以其独特的设计和出色的性能优势&#xff0c;成为许多音乐爱好者的首选。与传统的入耳式耳机相比&#xff0c;开放式耳机给用户带来更加舒适的佩戴体验。无需插入耳道&#xff0c;避免了长时间佩戴带来的不适感&#xff0c;让你可以尽情享受音乐而不受干扰。此外&…

Codeforces Round 901 (Div. 1) B. Jellyfish and Math(思维题/bfs)

题目 t(t<1e5)组样例&#xff0c;每次给出a,b,c,d,m(0<a,b,c,d,m<2的30次方) 初始时&#xff0c;(x,y)(a,b)&#xff0c;每次操作&#xff0c;你可以执行以下四种操作之一 ①xx&y&#xff0c;&为与 ②xx|y&#xff0c;|为或 ③yx^y&#xff0c;^为异或 …

心连心,同一个地方,同一个程序员

Hi,all, 现在开始所有一线城市和二线城市建立程序员交流群,为了防止病毒以及一些丧心病狂的广告人士,现在各大群以程序员为基础,入群者必须为程序员,严谨广告和僵尸人士入群,有想加群者加我微信,备注城市。比如想加入南京程序员交流群,备注南京,或者微信聊天窗口私聊…

基于 QT 实现一个 Ikun 专属桌面宠物

Step0、实现思路 想到的思路有两种&#xff1a; 1、使用 QT 的状态机模式&#xff0c;参考官网文档&#xff0c;这个模式的解耦最佳 2、使用原生 Wigets&#xff0c;将窗口设置为透明无框&#xff0c;循环播放桌面宠物的状态 本文采用第二种思路&#xff0c;实现一个极简版…

湖南特色农产品销售系统APP /基于android的农产品销售系统/基于android的购物系统

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的APP应运而生&#xff0c;各行各业相继进入信息管理时代&#x…

ThreeJS - 封装一个GLB模型展示组件(TypeScript)

一、引言 最近基于Three.JS&#xff0c;使用class封装了一个GLB模型展示&#xff0c;支持TypeScript、支持不同框架使用&#xff0c;具有多种功能。 &#xff08;下图展示一些基础的功能&#xff0c;可以自行扩展&#xff0c;比如光源等&#xff09; 二、主要代码 本模块依赖…

毕业设计选题uniapp+springboot新闻资讯小程序源码 开题 lw 调试

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人七年开发经验&#xff0c;擅长Java、Python、PHP、.NET、微信小程序、爬虫、大数据等&#xff0c;大家有这一块的问题可以一起交流&#xff01; &#x1f495;&…

【QT】自定义组件ui类添加到主ui界面方法

1.添加自定义组件到项目中 add new选择如下 写好类方法&#xff0c;确定即可 2.将新创建的ui类加入到主ui界面 选中新创建ui类的父类空块&#xff0c;右键选择提升为 选择并添加新创建的类

Tomcat Servlet

Tomcat & Servlet 一、What is “Tomcat”?二、 What is “Servlet”?1、HttpServlet2、HttpServletRequest3、HttpServletResponse 一、What is “Tomcat”? Tomcat 本质上是一个基于 TCP 协议的 HTTP 服务器。我们知道HTTP是一种应用层协议&#xff0c;是 HTTP 客户端…

PWN 基础篇 Write Up

PWN 05 解题过程 给了两个文件&#xff0c;一个是asm后缀&#xff0c;汇编代码文件&#xff0c;另外一个file看看&#xff0c;32位i静态编译可执行文件 分析一下汇编代码&#xff0c;里面包含有两个节&#xff0c;data节 该代码片段只展示了数据段的一部分&#xff0c;缺少了…

[VIM]VIM初步学习-3

3-1 编写 vim 配置&#xff0c;我的 vim 我做主_哔哩哔哩_bilibili

【GESP考级C++】1级样题 闰年统计

GSEP 1级样题 闰年统计 题目描述 小明刚刚学习了如何判断平年和闰年&#xff0c;他想知道两个年份之间&#xff08;包含起始年份和终止年份&#xff09;有几个闰年。你能帮帮他吗&#xff1f; 输入格式 输入一行&#xff0c;包含两个整数&#xff0c;分别表示起始年份和终止…

(四)动态阈值分割

文章目录 一、基本概念二、实例解析 一、基本概念 基于局部阈值分割的dyn_threshold()算子&#xff0c;适用于一些无法用单一灰度进行分割的情况&#xff0c;如背景比较复杂&#xff0c;有的部分比前景目标亮&#xff0c;或者有的部分比前景目标暗&#xff1b;又比如前景目标包…

数量关系 --- 方程

目录 一、代入排除法 例题 练习 二、数字特性 例题 练习 整除特性 例题 倍数特性 普通倍数 因子倍数 比例倍数 例题 练习 三、方程法 例题 练习 四、 不定方程&#xff08;组&#xff09; 例题 练习 一、代入排除法 例题 素数&#xff1a…