znlgis 博客

GIS开发与技术分享

第七章:三维图元系统

7.1 三维图元概述

7.1.1 三维图元分类

LightCAD的三维图元位于LightCAD.Core/Elements/Element3d/目录下,提供了完整的三维空间几何实体支持:

图元类型 类名 说明
三维直线 LcLine3d 三维空间中的线段
三维圆弧 LcArc3d 三维空间中的圆弧
三维圆 LcCircle3d 三维空间中的圆
三维曲线 LcCurve3d 通用三维曲线
三维轮廓 LcProfile3d 用于拉伸等操作的截面轮廓
工作平面 LcWorkPlane3d 自定义工作坐标系
三维组 LcGroup3 三维实体的分组容器
三维阵列 LcArray3 三维空间中的阵列复制
容器引用 LcContianerRef3 组件容器引用

7.1.2 二维与三维图元的关系

二维图元(在XY平面上)        三维图元(在任意空间位置)
LcLine    ──对应──>    LcLine3d
LcArc     ──对应──>    LcArc3d
LcCircle  ──对应──>    LcCircle3d
LcSpline  ──对应──>    LcCurve3d

三维图元在二维图元的基础上增加了Z坐标和空间方位信息,能够表示任意空间位置和方向的几何实体。

7.2 三维直线(LcLine3d)

7.2.1 数据结构

public class LcLine3d : LcEntity, IUpdateObject
{
    /// <summary>
    /// 起点(三维坐标)
    /// </summary>
    public Point3d StartPoint { get; set; }

    /// <summary>
    /// 终点(三维坐标)
    /// </summary>
    public Point3d EndPoint { get; set; }

    /// <summary>
    /// 长度
    /// </summary>
    public double Length => StartPoint.DistanceTo(EndPoint);

    /// <summary>
    /// 方向向量
    /// </summary>
    public Vector3d Direction =>
        new Vector3d(
            EndPoint.X - StartPoint.X,
            EndPoint.Y - StartPoint.Y,
            EndPoint.Z - StartPoint.Z
        ).Normalize();

    /// <summary>
    /// 中点
    /// </summary>
    public Point3d MidPoint => new Point3d(
        (StartPoint.X + EndPoint.X) / 2,
        (StartPoint.Y + EndPoint.Y) / 2,
        (StartPoint.Z + EndPoint.Z) / 2
    );

    public override string TypeName => "Line3d";

    /// <summary>
    /// 投影到XY平面获取二维线段
    /// </summary>
    public LcLine ToLine2d()
    {
        return new LcLine
        {
            StartPoint = StartPoint.ToPoint2d(),
            EndPoint = EndPoint.ToPoint2d()
        };
    }

    /// <summary>
    /// 计算点到三维直线的距离
    /// </summary>
    public double DistanceTo3d(Point3d point)
    {
        var ab = new Vector3d(
            EndPoint.X - StartPoint.X,
            EndPoint.Y - StartPoint.Y,
            EndPoint.Z - StartPoint.Z);
        var ap = new Vector3d(
            point.X - StartPoint.X,
            point.Y - StartPoint.Y,
            point.Z - StartPoint.Z);

        var cross = ab.Cross(ap);
        return cross.Length / ab.Length;
    }

    public override void TransformBy(Matrix4d matrix)
    {
        StartPoint = matrix.Transform(StartPoint);
        EndPoint = matrix.Transform(EndPoint);
    }
}

7.2.2 使用示例

// 创建一条在三维空间中的直线
var line3d = new LcLine3d
{
    StartPoint = new Point3d(0, 0, 0),
    EndPoint = new Point3d(100, 50, 75)
};

// 获取直线信息
Console.WriteLine($"长度: {line3d.Length}");
Console.WriteLine($"方向: {line3d.Direction}");
Console.WriteLine($"中点: {line3d.MidPoint}");

// 投影到二维
var line2d = line3d.ToLine2d();

7.3 三维圆弧(LcArc3d)

7.3.1 数据结构

三维圆弧需要额外的方向信息来确定其在空间中的位置:

public class LcArc3d : LcEntity, IUpdateObject
{
    /// <summary>
    /// 圆弧中心(三维坐标)
    /// </summary>
    public Point3d Center { get; set; }

    /// <summary>
    /// 半径
    /// </summary>
    public double Radius { get; set; }

    /// <summary>
    /// 圆弧所在平面的法向量
    /// </summary>
    public Vector3d Normal { get; set; } = Vector3d.ZAxis;

    /// <summary>
    /// 起始角度
    /// </summary>
    public double StartAngle { get; set; }

    /// <summary>
    /// 终止角度
    /// </summary>
    public double EndAngle { get; set; }

    /// <summary>
    /// X轴方向(定义角度参考方向)
    /// </summary>
    public Vector3d XDirection { get; set; } = Vector3d.XAxis;

    public override string TypeName => "Arc3d";

    /// <summary>
    /// 获取圆弧所在的平面
    /// </summary>
    public Plane GetPlane()
    {
        return new Plane(Center, Normal);
    }

    /// <summary>
    /// 获取三维起点
    /// </summary>
    public Point3d StartPoint3d
    {
        get
        {
            var yDir = Normal.Cross(XDirection).Normalize();
            return new Point3d(
                Center.X + Radius * (Math.Cos(StartAngle) * XDirection.X +
                    Math.Sin(StartAngle) * yDir.X),
                Center.Y + Radius * (Math.Cos(StartAngle) * XDirection.Y +
                    Math.Sin(StartAngle) * yDir.Y),
                Center.Z + Radius * (Math.Cos(StartAngle) * XDirection.Z +
                    Math.Sin(StartAngle) * yDir.Z)
            );
        }
    }

    /// <summary>
    /// 离散化为三维点序列
    /// </summary>
    public List<Point3d> Tessellate(int segments = 32)
    {
        var points = new List<Point3d>();
        var yDir = Normal.Cross(XDirection).Normalize();
        var sweep = EndAngle - StartAngle;
        if (sweep < 0) sweep += 2 * Math.PI;

        for (int i = 0; i <= segments; i++)
        {
            var t = (double)i / segments;
            var angle = StartAngle + sweep * t;
            var cos = Math.Cos(angle);
            var sin = Math.Sin(angle);

            points.Add(new Point3d(
                Center.X + Radius * (cos * XDirection.X + sin * yDir.X),
                Center.Y + Radius * (cos * XDirection.Y + sin * yDir.Y),
                Center.Z + Radius * (cos * XDirection.Z + sin * yDir.Z)
            ));
        }

        return points;
    }
}

7.4 三维圆(LcCircle3d)

7.4.1 数据结构

public class LcCircle3d : LcEntity, IUpdateObject
{
    /// <summary>
    /// 圆心(三维坐标)
    /// </summary>
    public Point3d Center { get; set; }

    /// <summary>
    /// 半径
    /// </summary>
    public double Radius { get; set; }

    /// <summary>
    /// 法向量(圆所在平面的法线)
    /// </summary>
    public Vector3d Normal { get; set; } = Vector3d.ZAxis;

    /// <summary>
    /// 周长
    /// </summary>
    public double Circumference => 2 * Math.PI * Radius;

    /// <summary>
    /// 面积
    /// </summary>
    public double Area => Math.PI * Radius * Radius;

    public override string TypeName => "Circle3d";

    /// <summary>
    /// 获取圆上指定角度的三维点
    /// </summary>
    public Point3d PointAt(double angle)
    {
        // 构建局部坐标系
        var xDir = GetLocalXDirection();
        var yDir = Normal.Cross(xDir).Normalize();

        return new Point3d(
            Center.X + Radius * (Math.Cos(angle) * xDir.X +
                Math.Sin(angle) * yDir.X),
            Center.Y + Radius * (Math.Cos(angle) * xDir.Y +
                Math.Sin(angle) * yDir.Y),
            Center.Z + Radius * (Math.Cos(angle) * xDir.Z +
                Math.Sin(angle) * yDir.Z)
        );
    }

    /// <summary>
    /// 构建局部X方向
    /// </summary>
    private Vector3d GetLocalXDirection()
    {
        // 选择一个与法向量不共线的向量
        var reference = Math.Abs(Normal.Dot(Vector3d.ZAxis)) < 0.9
            ? Vector3d.ZAxis : Vector3d.XAxis;
        return reference.Cross(Normal).Normalize();
    }

    /// <summary>
    /// 离散化为三维点序列
    /// </summary>
    public List<Point3d> Tessellate(int segments = 64)
    {
        var points = new List<Point3d>();
        for (int i = 0; i <= segments; i++)
        {
            var angle = 2 * Math.PI * i / segments;
            points.Add(PointAt(angle));
        }
        return points;
    }
}

7.5 工作平面(LcWorkPlane3d)

7.5.1 概念说明

工作平面是LightCAD三维建模中的重要概念。它定义了一个局部坐标系,允许用户在任意方向的平面上进行二维绘图操作,然后将结果映射到三维空间中。

7.5.2 数据结构

public class LcWorkPlane3d : LcEntity, IUpdateObject
{
    /// <summary>
    /// 工作平面原点
    /// </summary>
    public Point3d Origin { get; set; }

    /// <summary>
    /// 平面法向量
    /// </summary>
    public Vector3d Normal { get; set; } = Vector3d.ZAxis;

    /// <summary>
    /// 局部X轴方向
    /// </summary>
    public Vector3d XAxis { get; set; } = Vector3d.XAxis;

    /// <summary>
    /// 局部Y轴方向(自动计算)
    /// </summary>
    public Vector3d YAxis => Normal.Cross(XAxis).Normalize();

    /// <summary>
    /// 显示大小(工作平面网格的范围)
    /// </summary>
    public double DisplaySize { get; set; } = 1000;

    /// <summary>
    /// 网格间距
    /// </summary>
    public double GridSpacing { get; set; } = 10;

    /// <summary>
    /// 是否显示网格
    /// </summary>
    public bool ShowGrid { get; set; } = true;

    public override string TypeName => "WorkPlane3d";

    /// <summary>
    /// 将局部二维坐标转换为世界三维坐标
    /// </summary>
    public Point3d LocalToWorld(Point2d localPoint)
    {
        return new Point3d(
            Origin.X + localPoint.X * XAxis.X + localPoint.Y * YAxis.X,
            Origin.Y + localPoint.X * XAxis.Y + localPoint.Y * YAxis.Y,
            Origin.Z + localPoint.X * XAxis.Z + localPoint.Y * YAxis.Z
        );
    }

    /// <summary>
    /// 将世界三维坐标转换为局部二维坐标
    /// </summary>
    public Point2d WorldToLocal(Point3d worldPoint)
    {
        var v = new Vector3d(
            worldPoint.X - Origin.X,
            worldPoint.Y - Origin.Y,
            worldPoint.Z - Origin.Z
        );

        return new Point2d(
            v.Dot(XAxis),
            v.Dot(YAxis)
        );
    }

    /// <summary>
    /// 将三维点投影到工作平面上
    /// </summary>
    public Point3d ProjectPoint(Point3d point)
    {
        var local = WorldToLocal(point);
        return LocalToWorld(local);
    }

    /// <summary>
    /// 获取变换矩阵(局部到世界)
    /// </summary>
    public Matrix4d GetTransformMatrix()
    {
        return new Matrix4d(
            XAxis.X, YAxis.X, Normal.X, Origin.X,
            XAxis.Y, YAxis.Y, Normal.Y, Origin.Y,
            XAxis.Z, YAxis.Z, Normal.Z, Origin.Z,
            0, 0, 0, 1
        );
    }
}

7.5.3 使用示例

// 创建一个倾斜45度的工作平面
var workPlane = new LcWorkPlane3d
{
    Origin = new Point3d(0, 0, 100),
    Normal = new Vector3d(0, -Math.Sin(Math.PI / 4), Math.Cos(Math.PI / 4)),
    XAxis = Vector3d.XAxis,
    GridSpacing = 10,
    ShowGrid = true
};

// 在工作平面上创建一个矩形
var p1 = workPlane.LocalToWorld(new Point2d(0, 0));
var p2 = workPlane.LocalToWorld(new Point2d(100, 0));
var p3 = workPlane.LocalToWorld(new Point2d(100, 50));
var p4 = workPlane.LocalToWorld(new Point2d(0, 50));

var rect = new LcPolyline();
rect.AddVertex(new Point2d(p1.X, p1.Y));
rect.AddVertex(new Point2d(p2.X, p2.Y));
rect.AddVertex(new Point2d(p3.X, p3.Y));
rect.AddVertex(new Point2d(p4.X, p4.Y));
rect.IsClosed = true;

7.6 三维组(LcGroup3)

7.6.1 数据结构

LcGroup3实现了组合模式(Composite Pattern),将多个三维实体组织为一个逻辑单元:

public class LcGroup3 : LcEntity, IUpdateObject
{
    /// <summary>
    /// 子实体集合
    /// </summary>
    public List<LcEntity> Children { get; set; } = new();

    /// <summary>
    /// 组的变换矩阵
    /// </summary>
    public Matrix4d Transform { get; set; } = Matrix4d.Identity;

    /// <summary>
    /// 组名称
    /// </summary>
    public string GroupName { get; set; }

    public override string TypeName => "Group3";

    /// <summary>
    /// 添加子实体
    /// </summary>
    public void Add(LcEntity entity)
    {
        Children.Add(entity);
    }

    /// <summary>
    /// 移除子实体
    /// </summary>
    public bool Remove(LcEntity entity)
    {
        return Children.Remove(entity);
    }

    /// <summary>
    /// 将组展开为独立实体
    /// </summary>
    public List<LcEntity> Explode()
    {
        var result = new List<LcEntity>();
        foreach (var child in Children)
        {
            var clone = child.Clone();
            clone.TransformBy(Transform);
            result.Add(clone);
        }
        return result;
    }

    public override void TransformBy(Matrix4d matrix)
    {
        Transform = matrix * Transform;
    }

    public override BoundingBox2d Bounds
    {
        get
        {
            if (Children.Count == 0)
                return new BoundingBox2d();

            var bounds = Children[0].Bounds;
            for (int i = 1; i < Children.Count; i++)
            {
                bounds = bounds.Union(Children[i].Bounds);
            }
            return bounds;
        }
    }
}

7.7 三维阵列(LcArray3)

7.7.1 数据结构

LcArray3支持在三维空间中创建实体的参数化阵列复制:

public class LcArray3 : LcEntity, IUpdateObject
{
    /// <summary>
    /// 源实体
    /// </summary>
    public LcEntity SourceEntity { get; set; }

    /// <summary>
    /// 阵列类型
    /// </summary>
    public ArrayType Type { get; set; }

    /// <summary>
    /// X方向数量
    /// </summary>
    public int CountX { get; set; } = 1;

    /// <summary>
    /// Y方向数量
    /// </summary>
    public int CountY { get; set; } = 1;

    /// <summary>
    /// Z方向数量
    /// </summary>
    public int CountZ { get; set; } = 1;

    /// <summary>
    /// X方向间距
    /// </summary>
    public double SpacingX { get; set; }

    /// <summary>
    /// Y方向间距
    /// </summary>
    public double SpacingY { get; set; }

    /// <summary>
    /// Z方向间距
    /// </summary>
    public double SpacingZ { get; set; }

    /// <summary>
    /// 环形阵列中心
    /// </summary>
    public Point3d PolarCenter { get; set; }

    /// <summary>
    /// 环形阵列轴向量
    /// </summary>
    public Vector3d PolarAxis { get; set; } = Vector3d.ZAxis;

    /// <summary>
    /// 环形阵列总角度
    /// </summary>
    public double PolarAngle { get; set; } = 2 * Math.PI;

    /// <summary>
    /// 环形阵列数量
    /// </summary>
    public int PolarCount { get; set; }

    public override string TypeName => "Array3";

    /// <summary>
    /// 生成所有阵列实例
    /// </summary>
    public List<LcEntity> GenerateInstances()
    {
        return Type switch
        {
            ArrayType.Rectangular => GenerateRectangular(),
            ArrayType.Polar => GeneratePolar(),
            _ => new List<LcEntity>()
        };
    }

    private List<LcEntity> GenerateRectangular()
    {
        var instances = new List<LcEntity>();
        for (int ix = 0; ix < CountX; ix++)
        {
            for (int iy = 0; iy < CountY; iy++)
            {
                for (int iz = 0; iz < CountZ; iz++)
                {
                    if (ix == 0 && iy == 0 && iz == 0) continue;

                    var clone = SourceEntity.Clone();
                    var translation = Matrix4d.CreateTranslation(
                        ix * SpacingX,
                        iy * SpacingY,
                        iz * SpacingZ
                    );
                    clone.TransformBy(translation);
                    instances.Add(clone);
                }
            }
        }
        return instances;
    }

    private List<LcEntity> GeneratePolar()
    {
        var instances = new List<LcEntity>();
        var angleStep = PolarAngle / PolarCount;

        for (int i = 1; i < PolarCount; i++)
        {
            var clone = SourceEntity.Clone();
            var angle = angleStep * i;

            // 绕指定轴旋转
            var toOrigin = Matrix4d.CreateTranslation(
                -PolarCenter.X, -PolarCenter.Y, -PolarCenter.Z);
            var rotation = CreateRotationAroundAxis(PolarAxis, angle);
            var backToPos = Matrix4d.CreateTranslation(
                PolarCenter.X, PolarCenter.Y, PolarCenter.Z);

            clone.TransformBy(backToPos * rotation * toOrigin);
            instances.Add(clone);
        }
        return instances;
    }
}

public enum ArrayType
{
    Rectangular,  // 矩形阵列
    Polar         // 环形阵列
}

7.8 三维轮廓(LcProfile3d)

7.8.1 数据结构

LcProfile3d用于定义三维空间中的封闭轮廓,常用作拉伸、旋转等实体建模操作的截面:

public class LcProfile3d : LcEntity, IUpdateObject
{
    /// <summary>
    /// 轮廓所在的工作平面
    /// </summary>
    public LcWorkPlane3d WorkPlane { get; set; }

    /// <summary>
    /// 外轮廓曲线(必须闭合)
    /// </summary>
    public LcPolyline OuterLoop { get; set; }

    /// <summary>
    /// 内部孔洞轮廓(可选)
    /// </summary>
    public List<LcPolyline> InnerLoops { get; set; } = new();

    /// <summary>
    /// 轮廓面积(扣除孔洞)
    /// </summary>
    public double Area
    {
        get
        {
            var outerArea = OuterLoop.Area;
            var innerArea = InnerLoops.Sum(loop => loop.Area);
            return outerArea - innerArea;
        }
    }

    public override string TypeName => "Profile3d";

    /// <summary>
    /// 获取轮廓的三维点序列
    /// </summary>
    public List<Point3d> GetWorldPoints()
    {
        var points2d = OuterLoop.Tessellate();
        return points2d.Select(p => WorkPlane.LocalToWorld(p)).ToList();
    }

    /// <summary>
    /// 验证轮廓是否有效
    /// </summary>
    public bool IsValid()
    {
        if (OuterLoop == null || !OuterLoop.IsClosed) return false;
        if (OuterLoop.VertexCount < 3) return false;

        // 检查内部轮廓是否都在外部轮廓内
        foreach (var inner in InnerLoops)
        {
            if (!inner.IsClosed) return false;
            foreach (var vertex in inner.Vertices)
            {
                var polygon = OuterLoop.Vertices
                    .Select(v => v.Position).ToList();
                if (!GeometryUtils.PointInPolygon(vertex.Position, polygon))
                    return false;
            }
        }

        return true;
    }
}

7.9 容器引用(LcContianerRef3)

7.9.1 概念说明

LcContianerRef3是LightCAD中实现组件实例化和引用的机制。类似于AutoCAD中的块引用(Block Reference),它允许在文档中多次放置同一个组件定义的实例,而不必复制所有数据。

7.9.2 数据结构

public class LcContianerRef3 : LcEntity, IUpdateObject
{
    /// <summary>
    /// 引用的组件定义标识
    /// </summary>
    public LcGuid ComponentGuid { get; set; }

    /// <summary>
    /// 插入点(在世界坐标中的位置)
    /// </summary>
    public Point3d InsertionPoint { get; set; }

    /// <summary>
    /// 旋转角度
    /// </summary>
    public double Rotation { get; set; }

    /// <summary>
    /// 缩放因子
    /// </summary>
    public Vector3d Scale { get; set; } = new Vector3d(1, 1, 1);

    /// <summary>
    /// 完整的变换矩阵
    /// </summary>
    public Matrix4d InsertionMatrix
    {
        get
        {
            var scale = Matrix4d.CreateScale(Scale.X, Scale.Y, Scale.Z);
            var rotation = Matrix4d.CreateRotationZ(Rotation);
            var translation = Matrix4d.CreateTranslation(
                InsertionPoint.X, InsertionPoint.Y, InsertionPoint.Z);
            return translation * rotation * scale;
        }
    }

    /// <summary>
    /// 实例属性覆盖
    /// </summary>
    public Dictionary<string, object> PropertyOverrides { get; set; } = new();

    public override string TypeName => "ContainerRef3";

    /// <summary>
    /// 获取引用的组件定义
    /// </summary>
    public LcGroup3 GetDefinition()
    {
        // 从文档的块定义集合中查找
        return Document?.Blocks?.Find(ComponentGuid);
    }

    /// <summary>
    /// 展开引用为独立实体
    /// </summary>
    public List<LcEntity> Explode()
    {
        var definition = GetDefinition();
        if (definition == null) return new List<LcEntity>();

        var result = new List<LcEntity>();
        foreach (var child in definition.Children)
        {
            var clone = child.Clone();
            clone.TransformBy(InsertionMatrix);
            result.Add(clone);
        }
        return result;
    }
}

7.10 三维图元的坐标变换

7.10.1 世界坐标与局部坐标

在三维建模中,坐标变换是核心操作。LightCAD中的三维实体需要在不同坐标系之间转换:

// 世界坐标系(WCS)
// 全局固定的笛卡尔坐标系

// 用户坐标系(UCS)
// 用户自定义的坐标系,通过工作平面定义

// 对象坐标系(OCS)
// 实体自身的局部坐标系

// WCS → OCS 变换
public Point3d WorldToObject(Point3d worldPoint, Matrix4d objectTransform)
{
    var inverseTransform = objectTransform.Inverse();
    return inverseTransform.Transform(worldPoint);
}

// OCS → WCS 变换
public Point3d ObjectToWorld(Point3d objectPoint, Matrix4d objectTransform)
{
    return objectTransform.Transform(objectPoint);
}

7.10.2 常用三维变换

// 绕任意轴旋转
public static Matrix4d RotateAroundAxis(Point3d point, Vector3d axis, double angle)
{
    var toOrigin = Matrix4d.CreateTranslation(-point.X, -point.Y, -point.Z);
    var backToPos = Matrix4d.CreateTranslation(point.X, point.Y, point.Z);

    var n = axis.Normalize();
    var cos = Math.Cos(angle);
    var sin = Math.Sin(angle);
    var t = 1 - cos;

    var rotation = new Matrix4d(
        t * n.X * n.X + cos,       t * n.X * n.Y - sin * n.Z, t * n.X * n.Z + sin * n.Y, 0,
        t * n.X * n.Y + sin * n.Z, t * n.Y * n.Y + cos,       t * n.Y * n.Z - sin * n.X, 0,
        t * n.X * n.Z - sin * n.Y, t * n.Y * n.Z + sin * n.X, t * n.Z * n.Z + cos,       0,
        0,                          0,                          0,                          1
    );

    return backToPos * rotation * toOrigin;
}

// 三维镜像(关于平面镜像)
public static Matrix4d MirrorAboutPlane(Plane plane)
{
    var n = plane.Normal.Normalize();
    var d = -n.Dot(new Vector3d(
        plane.Origin.X, plane.Origin.Y, plane.Origin.Z));

    return new Matrix4d(
        1 - 2 * n.X * n.X, -2 * n.X * n.Y,     -2 * n.X * n.Z,     -2 * n.X * d,
        -2 * n.X * n.Y,     1 - 2 * n.Y * n.Y,  -2 * n.Y * n.Z,     -2 * n.Y * d,
        -2 * n.X * n.Z,     -2 * n.Y * n.Z,      1 - 2 * n.Z * n.Z,  -2 * n.Z * d,
        0,                   0,                    0,                    1
    );
}

7.11 本章小结

本章详细介绍了LightCAD的三维图元系统。三维图元在二维图元的基础上增加了Z坐标和空间方位信息,能够表示任意空间位置和方向的几何实体。工作平面(LcWorkPlane3d)提供了在任意方向的平面上进行绘图的能力,三维组(LcGroup3)和阵列(LcArray3)实现了实体的组织和批量复制,容器引用(LcContianerRef3)提供了高效的组件实例化机制。这些三维图元为后续的实体建模和视图构建奠定了基础。


上一章第六章:二维图元系统

下一章第八章:实体建模系统