You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
}

代码说明

  1. Surface结构体:针对轴对齐平面设计,用PlaneType标记平面所属轴,FixedCoordinate存储平面在对应轴上的固定值,另外四个字段定义平面在其余两轴的范围。
  2. GetCubeSides函数:将立方体拆解为4个垂直面(可扩展为6个面),每个面都转换为Surface结构体实例。
  3. LineIntersectsSurface函数:
    • 将角度转为弧度,计算太阳射线的方向向量
    • 通过平面方程计算射线与平面的交点参数t,仅考虑t>0的情况(射线前进方向)
    • 判断交点是否落在表面的范围内,使用1e-9的精度容差处理浮点计算误差
  4. IsSunVisible函数:遍历所有障碍物的表面,只要有一个表面与射线相交,就返回false(太阳被遮挡),否则返回true。

内容的提问来源于stack exchange,提问作者ygoe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 22:09:15