一种基于体素的射线检测
效果
基于体素的射线检测
一个漏检的射线检测
从起点一直递增指定步长即可得到一个稀疏的检测
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…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...

使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...