一种基于体素的射线检测
效果
基于体素的射线检测
一个漏检的射线检测
从起点一直递增指定步长即可得到一个稀疏的检测
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_EDITORisDraw = false;
#endiffloat 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_EDITORint index = 0;if (isDraw){Debug.DrawLine(from, to, Color.red);}
#endifwhile (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_EDITORif (isDraw){Gizmos.color = Color.grey;Gizmos.DrawWireCube(origin, Vector3.one);UnityEditor.Handles.Label(origin, $"[{index++}]");}
#endifif (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_EDITORif (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_EDITORif (isDraw){Gizmos.DrawWireSphere(enterPoint, 0.05f);}int index = 0;
#endifwhile (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_EDITORif (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++}]");}
#endifif (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_EDITORif (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;}
}
相关文章:

一种基于体素的射线检测
效果 基于体素的射线检测 一个漏检的射线检测 从起点一直递增指定步长即可得到一个稀疏的检测 bool Raycast(Vector3 from, Vector3 forword, float maxDistance){int loop 6666;Vector3 pos from;Debug.DrawLine(from, from forword * maxDistance, Color.red);while (loo…...
利用Docker安装Protostar
文章目录 一、Protostar介绍二、Ubuntu下安装docker三、安装Protostar 一、Protostar介绍 Protostar是一个免费的Linux镜像演练环境,包含五个系列共23道漏洞分析和利用实战题目。 Protostar的安装有两种方式 第一种是下载镜像并安装虚拟机https://github.com/Exp…...
go基础语法10问
1.使用值为 nil 的 slice、map会发生啥 允许对值为 nil 的 slice 添加元素,但对值为 nil 的 map 添加元素,则会造成运行时 panic。 // map 错误示例 func main() {var m map[string]intm["one"] 1 // error: panic: assignment to entry i…...

SpringCloud + SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题
title: “SpringCloud SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题” createTime: 2021-11-24T10:27:5708:00 updateTime: 2021-11-24T10:27:5708:00 draft: false author: “Atomicyo” tags: [“tomcat”] categories: [“java”] description: …...
vim基本操作
功能: 命令行模式下的文本编辑器。根据文件扩展名自动判别编程语言。支持代码缩进、代码高亮等功能。使用方式:vim filename 如果已有该文件,则打开它。 如果没有该文件,则打开个一个新的文件,并命名为filename 模式…...

Drift plus penalty 漂移加惩罚Part1——介绍和工作原理
文章目录 正文Methodology 方法论Origins and applications 起源和应用How it works 它是怎样工作的The stochastic optimization problem 随机优化问题Virtual queues 虚拟队列The drift-plus-penalty expression 漂移加惩罚表达式Drift-plus-penalty algorithmApproximate sc…...

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

jvm介绍
1. JVM是什么 JVM是Java Virtual Machine的缩写,即咱们经常提到的Java虚拟机。虚拟机是一种抽象化的计算机,有着自己完善的硬件架构,如处理器、堆栈等,具体有什么咱们不做了解。目前我们只需要知道想要运行Java文件,必…...

数据结构与算法课后题-第三章(顺序队和链队)
#include <iostream> //引入头文件 using namespace std;typedef int Elemtype;#define Maxsize 5 #define ERROR 0 #define OK 1typedef struct {Elemtype data[Maxsize];int front, rear;int tag; }SqQueue;void InitQueue(SqQueue& Q) //初始化队列 {Q.rear …...

SSM - Springboot - MyBatis-Plus 全栈体系(十六)
第三章 MyBatis 三、MyBatis 多表映射 2. 对一映射 2.1 需求说明 根据 ID 查询订单,以及订单关联的用户的信息! 2.2 OrderMapper 接口 public interface OrderMapper {Order selectOrderWithCustomer(Integer orderId); }2.3 OrderMapper.xml 配置…...

k8s--storageClass自动创建PV
文章目录 一、storageClass自动创建PV1.1 安装NFS1.2 创建nfs storageClass1.3 测试自动创建pv 一、storageClass自动创建PV 这里使用NFS实现 1.1 安装NFS 安装nfs-server: sh nfs_install.sh /mnt/data03 10.60.41.0/24nfs_install.sh #!/bin/bash### How to i…...

7.3 调用函数
前言: 思维导图: 7.3.1 函数调用的形式 我的笔记: 函数调用的形式 在C语言中,调用函数是一种常见的操作,主要有以下几种调用方式: 1. 函数调用语句 此时,函数调用独立存在,作为…...
如果使用pprof来进行性能的观测和优化
1. 分析性能瓶颈 在开始优化之前,首先需要确定你的程序的性能瓶颈在哪里。使用性能分析工具(例如 Go 的内置 pprof 包)来检测程序中消耗时间和内存的地方。这可以帮助你确定需要优化的具体部分。 2. 选择适当的数据结构和算法 选择正确的数…...

在移动固态硬盘上安装Ubuntu系统和ROS2
目录 原视频准备烧录 原视频 b站鱼香ros 准备 1.在某宝上买一个usb移动固态硬盘或固态U盘,至少64G 2.下载鱼香ros烧录工具 下载第二个就行了,不然某网盘的速度下载全部要一天 下载后,选择FishROS2OS制作工具压缩包,进行解压…...
【iptables 实战】02 iptables常用命令
一、iptables中基本的命令参数 -P 设置默认策略-F 清空规则链-L 查看规则链-A 在规则链的末尾加入新规则-I num 在规则链的头部加入新规则-D num 删除某一条规则-s 匹配来源地址IP/MASK,加叹号“!”表示除这个IP外-d 匹配目标地址-i 网卡名称 匹配从这块…...
webview_flutter
查看webview内核 https://liulanmi.com/labs/core.html h5中获取设备 https://cloud.tencent.com/developer/ask/sof/105938013 https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/mediaDevices web资源部署后navigator获取不到mediaDevices实例的解决方案&…...

【GESP考级C++】1级样题 闰年统计
GSEP 1级样题 闰年统计 题目描述 小明刚刚学习了如何判断平年和闰年,他想知道两个年份之间(包含起始年份和终止年份)有几个闰年。你能帮帮他吗? 输入格式 输入一行,包含两个整数,分别表示起始年份和终止…...

CentOS密码重置
背景: 我有一个CentOS虚拟机,但是密码忘记了,偶尔记起可以重置密码,于是今天尝试记录一下,又因为我最近记性比较差,所以必须要记录一下。 过程: 1、在引导菜单界面(grubÿ…...

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

国庆day2---select实现服务器并发
select.c: #include <myhead.h>#define ERR_MSG(msg) do{\fprintf(stderr,"__%d__:",__LINE__);\perror(msg);\ }while(0)#define IP "192.168.1.3" #define PORT 8888int main(int argc, const char *argv[]) {//创建报式套接字socketi…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

【WiFi帧结构】
文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成:MAC头部frame bodyFCS,其中MAC是固定格式的,frame body是可变长度。 MAC头部有frame control,duration,address1,address2,addre…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...