3D轴对齐表面与直线相交检测算法及C#实现需求
3D轴对齐表面与太阳射线相交检测实现
需求与约束
- 开发应用判断建筑窗户是否被外部障碍物遮挡:从窗户内表面扫描点向太阳方向发射射线,检测是否与障碍物表面相交
- 已获取太阳方位角(0°=正北/Y轴正方向,90°=正东/X轴正方向)和仰角
- 所有障碍物、窗户表面均为轴对齐(与X/Y/Z平面平行),复杂障碍物可简化为立方体
- 使用OpenSCAD构建场景并提取坐标数据
完整C#实现代码
struct Point3D { public double X; public double Y; public double Z; } struct Surface { // 标记表面所属的轴对齐平面类型 public enum PlaneType { X, Y, Z } public PlaneType Type; // 平面在对应轴上的固定坐标值(如X平面的X坐标) public double FixedCoordinate; // 平面在另外两个轴上的范围(比如X平面对应Y/Z的最小、最大值) public double Min1; public double Max1; public double Min2; public double Max2; } struct Cube { // 立方体原点(最小坐标点) public double X0; public double Y0; public double Z0; // 立方体在各轴上的尺寸 public double DX; public double DY; public double DZ; } bool IsSunVisible(Point3D pov, double azimuth, double elevation, Cube[] obstacles) { foreach (var obstacle in obstacles) { var sides = GetCubeSides(obstacle); foreach (var side in sides) { if (LineIntersectsSurface(pov, azimuth, elevation, side)) return false; } } return true; } bool LineIntersectsSurface(Point3D origin, double azimuth, double elevation, Surface surface) { // 将角度转换为弧度 double azimuthRad = azimuth * Math.PI / 180.0; double elevationRad = elevation * Math.PI / 180.0; // 计算太阳射线的方向向量 // 方位角:0°=Y正方向,90°=X正方向;仰角:从XY平面向上的角度 double dirX = Math.Sin(azimuthRad) * Math.Cos(elevationRad); double dirY = Math.Cos(azimuthRad) * Math.Cos(elevationRad); double dirZ = Math.Sin(elevationRad); // 计算射线与平面的交点参数t(t>0表示在射线前进方向上) double t = double.NaN; double intersect1, intersect2; switch (surface.Type) { case Surface.PlaneType.X: // 平面方程:X = FixedCoordinate if (Math.Abs(dirX) < 1e-9) // 射线与平面平行,无交点 return false; t = (surface.FixedCoordinate - origin.X) / dirX; if (t <= 1e-9) // 交点在原点后方或重合,不算遮挡 return false; // 计算交点的Y、Z坐标并判断是否在表面范围内 intersect1 = origin.Y + t * dirY; intersect2 = origin.Z + t * dirZ; return intersect1 >= surface.Min1 - 1e-9 && intersect1 <= surface.Max1 + 1e-9 && intersect2 >= surface.Min2 - 1e-9 && intersect2 <= surface.Max2 + 1e-9; case Surface.PlaneType.Y: // 平面方程:Y = FixedCoordinate if (Math.Abs(dirY) < 1e-9) return false; t = (surface.FixedCoordinate - origin.Y) / dirY; if (t <= 1e-9) return false; intersect1 = origin.X + t * dirX; intersect2 = origin.Z + t * dirZ; return intersect1 >= surface.Min1 - 1e-9 && intersect1 <= surface.Max1 + 1e-9 && intersect2 >= surface.Min2 - 1e-9 && intersect2 <= surface.Max2 + 1e-9; case Surface.PlaneType.Z: // 平面方程:Z = FixedCoordinate if (Math.Abs(dirZ) < 1e-9) return false; t = (surface.FixedCoordinate - origin.Z) / dirZ; if (t <= 1e-9) return false; intersect1 = origin.X + t * dirX; intersect2 = origin.Y + t * dirY; return intersect1 >= surface.Min1 - 1e-9 && intersect1 <= surface.Max1 + 1e-9 && intersect2 >= surface.Min2 - 1e-9 && intersect2 <= surface.Max2 + 1e-9; default: return false; } } Surface[] GetCubeSides(Cube cube) { var sides = new List<Surface>(); // 生成X轴方向的两个垂直面 sides.Add(new Surface { Type = Surface.PlaneType.X, FixedCoordinate = cube.X0, Min1 = cube.Y0, Max1 = cube.Y0 + cube.DY, Min2 = cube.Z0, Max2 = cube.Z0 + cube.DZ }); sides.Add(new Surface { Type = Surface.PlaneType.X, FixedCoordinate = cube.X0 + cube.DX, Min1 = cube.Y0, Max1 = cube.Y0 + cube.DY, Min2 = cube.Z0, Max2 = cube.Z0 + cube.DZ }); // 生成Y轴方向的两个垂直面 sides.Add(new Surface { Type = Surface.PlaneType.Y, FixedCoordinate = cube.Y0, Min1 = cube.X0, Max1 = cube.X0 + cube.DX, Min2 = cube.Z0, Max2 = cube.Z0 + cube.DZ }); sides.Add(new Surface { Type = Surface.PlaneType.Y, FixedCoordinate = cube.Y0 + cube.DY, Min1 = cube.X0, Max1 = cube.X0 + cube.DX, Min2 = cube.Z0, Max2 = cube.Z0 + cube.DZ }); // 如需检测上下表面,可取消下方注释 // // 生成Z轴方向的两个水平面 // sides.Add(new Surface // { // Type = Surface.PlaneType.Z, // FixedCoordinate = cube.Z0, // Min1 = cube.X0, // Max1 = cube.X0 + cube.DX, // Min2 = cube.Y0, // Max2 = cube.Y0 + cube.DY // }); // sides.Add(new Surface // { // Type = Surface.PlaneType.Z, // FixedCoordinate = cube.Z0 + cube.DZ, // Min1 = cube.X0, // Max1 = cube.X0 + cube.DX, // Min2 = cube.Y0, // Max2 = cube.Y0 + cube.DY // }); return sides.ToArray(); }
代码说明
- Surface结构体:针对轴对齐平面设计,用
PlaneType标记平面所属轴,FixedCoordinate存储平面在对应轴上的固定值,另外四个字段定义平面在其余两轴的范围。 - GetCubeSides函数:将立方体拆解为4个垂直面(可扩展为6个面),每个面都转换为Surface结构体实例。
- LineIntersectsSurface函数:
- 将角度转为弧度,计算太阳射线的方向向量
- 通过平面方程计算射线与平面的交点参数
t,仅考虑t>0的情况(射线前进方向) - 判断交点是否落在表面的范围内,使用
1e-9的精度容差处理浮点计算误差
- IsSunVisible函数:遍历所有障碍物的表面,只要有一个表面与射线相交,就返回
false(太阳被遮挡),否则返回true。
内容的提问来源于stack exchange,提问作者ygoe
相关产品推荐
相关产品推荐

