znlgis 博客

GIS开发与技术分享

第十一章:视图构建系统

11.1 视图构建概述

11.1.1 ViewBuilder的定位

视图构建系统是LightCAD.Runtime模块中最核心也最复杂的子系统。它负责将三维模型转换为各种二维投影视图,是CAD工程制图的关键技术。仅ViewBuilder.cs一个文件就达到71KB,ProjectioinProcessor.cs达到39KB,可见其复杂程度。

11.1.2 视图类型

LightCAD支持以下视图类型:

视图类型 目录/类 说明
全局俯视图 GlobalTopView/ 从正上方向下的投影视图
立面视图 ElevationView/ 从正面或侧面的投影视图
平面详图 PlaneDetailView/ 指定平面的详细视图
剖面视图 SectionView/ 通过切割平面的截面视图
投影视图 ProjectionApp/ 通用的正交投影框架

11.1.3 模块结构

LightCAD.Runtime/ViewBuilder/
├── ViewBuilder.cs              # 主构建器(71KB)
├── ProjectioinProcessor.cs     # 投影处理器(39KB)
├── PolygonExt.cs               # 多边形扩展(22KB)
├── PrjResultCollector.cs       # 投影结果收集器
├── BrepViewBuilder.cs          # B-Rep视图构建器
├── GpuVisibleFilter.cs         # GPU可见性过滤器
├── GlobalTopView/              # 全局俯视图
├── ElevationView/              # 立面视图
├── PlaneDetailView/            # 平面详图
├── SectionView/                # 剖面视图
└── ProjectionApp/              # 投影应用框架

11.2 投影处理器(ProjectioinProcessor)

11.2.1 投影原理

正交投影将三维空间中的对象投影到二维平面上。投影方向由视图的法线方向决定:

public class ProjectioinProcessor
{
    /// <summary>
    /// 投影方向
    /// </summary>
    public Vector3d ProjectionDirection { get; set; }

    /// <summary>
    /// 投影平面的法线
    /// </summary>
    public Vector3d PlaneNormal => ProjectionDirection.Normalize();

    /// <summary>
    /// 投影平面上的U方向(水平)
    /// </summary>
    public Vector3d UDirection { get; set; }

    /// <summary>
    /// 投影平面上的V方向(垂直)
    /// </summary>
    public Vector3d VDirection { get; set; }

    /// <summary>
    /// 将三维点投影到二维平面
    /// </summary>
    public Point2d Project(Point3d point)
    {
        var u = point.X * UDirection.X +
                point.Y * UDirection.Y +
                point.Z * UDirection.Z;
        var v = point.X * VDirection.X +
                point.Y * VDirection.Y +
                point.Z * VDirection.Z;
        return new Point2d(u, v);
    }

    /// <summary>
    /// 将三维线段投影为二维线段
    /// </summary>
    public LcLine ProjectLine(LcLine3d line3d)
    {
        var start2d = Project(line3d.StartPoint);
        var end2d = Project(line3d.EndPoint);
        return new LcLine
        {
            StartPoint = start2d,
            EndPoint = end2d
        };
    }

    /// <summary>
    /// 将三维圆投影为二维圆或椭圆
    /// </summary>
    public LcEntity ProjectCircle(LcCircle3d circle3d)
    {
        var normalDot = Math.Abs(
            circle3d.Normal.Dot(ProjectionDirection.Normalize()));

        if (normalDot > 0.999)
        {
            // 圆的法线与投影方向平行 → 投影为圆
            return new LcCircle
            {
                Center = Project(circle3d.Center),
                Radius = circle3d.Radius
            };
        }
        else if (normalDot < 0.001)
        {
            // 圆的法线与投影方向垂直 → 投影为线段
            var center2d = Project(circle3d.Center);
            var dir = circle3d.Normal.Cross(ProjectionDirection).Normalize();
            var dir2d = new Vector2d(
                dir.Dot(UDirection), dir.Dot(VDirection)).Normalize();

            return new LcLine
            {
                StartPoint = center2d - dir2d * circle3d.Radius,
                EndPoint = center2d + dir2d * circle3d.Radius
            };
        }
        else
        {
            // 一般情况 → 投影为椭圆
            return ProjectCircleAsEllipse(circle3d);
        }
    }

    /// <summary>
    /// 设置标准投影方向
    /// </summary>
    public void SetupProjection(StandardView view)
    {
        switch (view)
        {
            case StandardView.Top:
                ProjectionDirection = -Vector3d.ZAxis;
                UDirection = Vector3d.XAxis;
                VDirection = Vector3d.YAxis;
                break;
            case StandardView.Front:
                ProjectionDirection = Vector3d.YAxis;
                UDirection = Vector3d.XAxis;
                VDirection = Vector3d.ZAxis;
                break;
            case StandardView.Right:
                ProjectionDirection = -Vector3d.XAxis;
                UDirection = Vector3d.YAxis;
                VDirection = Vector3d.ZAxis;
                break;
        }
    }
}

11.3 ViewBuilder主构建器

11.3.1 视图构建流程

public class ViewBuilder
{
    private ProjectioinProcessor projector;
    private GpuVisibleFilter visibilityFilter;
    private PrjResultCollector resultCollector;

    /// <summary>
    /// 构建视图的主入口
    /// </summary>
    public ViewResult BuildView(ViewBuildParams buildParams)
    {
        // 第1步:设置投影参数
        projector = new ProjectioinProcessor();
        projector.SetupProjection(buildParams.ViewDirection);

        // 第2步:收集需要投影的三维实体
        var entities3d = CollectEntities(buildParams.Document);

        // 第3步:可见性过滤
        var visibleEntities = visibilityFilter.Filter(
            entities3d, buildParams.ViewDirection);

        // 第4步:投影每个实体
        var projectedEntities = new List<ProjectedEntity>();
        foreach (var entity in visibleEntities)
        {
            var projected = ProjectEntity(entity);
            projectedEntities.Add(projected);
        }

        // 第5步:隐藏线处理
        if (buildParams.HiddenLineRemoval)
        {
            ProcessHiddenLines(projectedEntities);
        }

        // 第6步:收集结果
        return resultCollector.Collect(projectedEntities);
    }

    /// <summary>
    /// 投影单个实体
    /// </summary>
    private ProjectedEntity ProjectEntity(LcEntity entity)
    {
        var result = new ProjectedEntity
        {
            SourceEntity = entity,
            VisibleLines = new List<LcLine>(),
            HiddenLines = new List<LcLine>(),
            SilhouetteLines = new List<LcLine>()
        };

        switch (entity)
        {
            case LcSolid3d solid:
                ProjectSolid(solid, result);
                break;
            case LcLine3d line:
                result.VisibleLines.Add(projector.ProjectLine(line));
                break;
            case LcCircle3d circle:
                var projected = projector.ProjectCircle(circle);
                // 转为线段集合
                break;
        }

        return result;
    }

    /// <summary>
    /// 投影实体(最复杂的情况)
    /// </summary>
    private void ProjectSolid(LcSolid3d solid, ProjectedEntity result)
    {
        var faces = solid.GetFaces();
        var edges = solid.GetEdges();

        // 对每条边进行投影
        foreach (var edge in edges)
        {
            var projectedEdge = projector.ProjectLine(
                new LcLine3d
                {
                    StartPoint = edge.StartPoint,
                    EndPoint = edge.EndPoint
                });

            // 判断边的类型(轮廓线/可见线/隐藏线)
            var edgeType = ClassifyEdge(edge, faces, projector);

            switch (edgeType)
            {
                case EdgeClassification.Visible:
                    result.VisibleLines.Add(projectedEdge);
                    break;
                case EdgeClassification.Hidden:
                    result.HiddenLines.Add(projectedEdge);
                    break;
                case EdgeClassification.Silhouette:
                    result.SilhouetteLines.Add(projectedEdge);
                    break;
            }
        }

        // 生成轮廓线(曲面体的边界投影)
        GenerateSilhouetteLines(solid, result);
    }
}

public class ViewBuildParams
{
    public LcDocument Document { get; set; }
    public StandardView ViewDirection { get; set; }
    public bool HiddenLineRemoval { get; set; } = true;
    public double Scale { get; set; } = 1.0;
    public Point2d ViewCenter { get; set; }
}

public class ViewResult
{
    public List<LcLine> VisibleLines { get; set; }
    public List<LcLine> HiddenLines { get; set; }
    public List<LcLine> SilhouetteLines { get; set; }
    public BoundingBox2d Bounds { get; set; }
}

11.4 隐藏线消除

11.4.1 算法概述

隐藏线消除(Hidden Line Removal, HLR)是工程制图中的核心算法,用于确定从特定视角观察时哪些线是可见的,哪些线被其他实体遮挡:

public class HiddenLineProcessor
{
    /// <summary>
    /// 处理隐藏线
    /// </summary>
    public void Process(List<ProjectedEntity> entities,
                        Vector3d viewDirection)
    {
        // 按深度排序实体(从近到远)
        var sortedEntities = entities
            .OrderBy(e => GetDepth(e, viewDirection))
            .ToList();

        // 对每条线段,检查是否被其他实体遮挡
        for (int i = 0; i < sortedEntities.Count; i++)
        {
            var currentEntity = sortedEntities[i];

            foreach (var line in currentEntity.VisibleLines.ToList())
            {
                // 检查这条线是否被前面的实体遮挡
                for (int j = 0; j < i; j++)
                {
                    var occluder = sortedEntities[j];
                    var clippedSegments = ClipLineAgainstEntity(
                        line, occluder, viewDirection);

                    if (clippedSegments.hidden.Count > 0)
                    {
                        // 将被遮挡的部分移到隐藏线列表
                        currentEntity.VisibleLines.Remove(line);
                        currentEntity.VisibleLines
                            .AddRange(clippedSegments.visible);
                        currentEntity.HiddenLines
                            .AddRange(clippedSegments.hidden);
                    }
                }
            }
        }
    }
}

11.5 B-Rep视图构建器

11.5.1 B-Rep概念

B-Rep(Boundary Representation,边界表示)是实体建模中的一种表示方法,通过面、边和顶点来描述实体的边界:

public class BrepViewBuilder
{
    /// <summary>
    /// 从B-Rep实体生成投影视图
    /// </summary>
    public ViewResult BuildFromBrep(LcSolid3d solid,
                                     ProjectioinProcessor projector)
    {
        var result = new ViewResult
        {
            VisibleLines = new List<LcLine>(),
            HiddenLines = new List<LcLine>(),
            SilhouetteLines = new List<LcLine>()
        };

        var faces = solid.GetFaces();
        var edges = solid.GetEdges();

        // 分类每个面是否面向观察者
        var facingMap = new Dictionary<SolidFace, bool>();
        foreach (var face in faces)
        {
            var dot = face.Normal.Dot(projector.PlaneNormal);
            facingMap[face] = dot < 0; // 面向观察者
        }

        // 分类每条边
        foreach (var edge in edges)
        {
            var adjacentFaces = GetAdjacentFaces(edge, faces);
            var edgeType = ClassifyEdge(adjacentFaces, facingMap);

            var projectedLine = projector.ProjectLine(
                new LcLine3d
                {
                    StartPoint = edge.StartPoint,
                    EndPoint = edge.EndPoint
                });

            switch (edgeType)
            {
                case EdgeType.Contour:
                    // 轮廓线:一个相邻面朝前,一个朝后
                    result.VisibleLines.Add(projectedLine);
                    break;
                case EdgeType.Crease:
                    // 折线:两个相邻面都朝前
                    result.VisibleLines.Add(projectedLine);
                    break;
                case EdgeType.Hidden:
                    // 隐藏线:两个相邻面都朝后
                    result.HiddenLines.Add(projectedLine);
                    break;
            }
        }

        return result;
    }
}

11.6 具体视图类型

11.6.1 全局俯视图(GlobalTopView)

public class GlobalTopViewBuilder
{
    /// <summary>
    /// 创建俯视图
    /// </summary>
    public ViewResult Build(LcDocument document, TopViewParams viewParams)
    {
        var builder = new ViewBuilder();
        var result = builder.BuildView(new ViewBuildParams
        {
            Document = document,
            ViewDirection = StandardView.Top,
            HiddenLineRemoval = viewParams.ShowHiddenLines,
            Scale = viewParams.Scale
        });

        // 添加标注
        if (viewParams.IncludeDimensions)
        {
            AddAutoDimensions(result, document);
        }

        // 添加标高标注
        if (viewParams.ShowElevations)
        {
            AddElevationMarks(result, document);
        }

        return result;
    }
}

11.6.2 立面视图(ElevationView)

public class ElevationViewBuilder
{
    /// <summary>
    /// 创建立面视图
    /// </summary>
    public ViewResult Build(LcDocument document,
                            ElevationViewParams viewParams)
    {
        // 确定立面方向
        var direction = viewParams.Direction switch
        {
            ElevationDirection.South => StandardView.Front,
            ElevationDirection.North => StandardView.Back,
            ElevationDirection.East => StandardView.Right,
            ElevationDirection.West => StandardView.Left,
            _ => StandardView.Front
        };

        var builder = new ViewBuilder();
        var result = builder.BuildView(new ViewBuildParams
        {
            Document = document,
            ViewDirection = direction,
            HiddenLineRemoval = true,
            Scale = viewParams.Scale
        });

        // 添加地面线
        AddGroundLine(result);

        // 添加标高标注
        AddElevationAnnotations(result, viewParams);

        return result;
    }
}

public enum ElevationDirection
{
    South,  // 南立面
    North,  // 北立面
    East,   // 东立面
    West    // 西立面
}

11.6.3 剖面视图(SectionView)

public class SectionViewBuilder
{
    /// <summary>
    /// 创建剖面视图
    /// </summary>
    public SectionViewResult Build(LcDocument document,
                                    SectionViewParams viewParams)
    {
        var result = new SectionViewResult();

        // 定义剖切平面
        var sectionPlane = new Plane(
            viewParams.PlanePoint,
            viewParams.PlaneNormal
        );

        // 对每个实体进行剖切
        foreach (var entity in document.Entities)
        {
            if (entity is LcSolid3d solid)
            {
                var sectionResult = CutSolid(solid, sectionPlane);

                // 剖切线(填充区域的轮廓)
                result.SectionLines.AddRange(sectionResult.Outlines);

                // 剖面填充
                if (viewParams.ShowHatching)
                {
                    result.HatchRegions.AddRange(
                        GenerateHatching(sectionResult.Outlines,
                            viewParams.HatchPattern));
                }
            }
        }

        // 投影剖切面后方的可见内容
        if (viewParams.ShowBeyondSection)
        {
            var behindResult = ProjectBehindSection(
                document, sectionPlane, viewParams.ViewDirection);
            result.ProjectedLines.AddRange(behindResult);
        }

        return result;
    }

    /// <summary>
    /// 对实体进行剖切
    /// </summary>
    private SectionCutResult CutSolid(LcSolid3d solid, Plane sectionPlane)
    {
        var result = new SectionCutResult();
        var meshData = solid.GenerateMesh();

        // 遍历所有三角形,找出与剖切平面相交的
        for (int i = 0; i < meshData.Indices.Count; i += 3)
        {
            var v0 = meshData.Vertices[meshData.Indices[i]];
            var v1 = meshData.Vertices[meshData.Indices[i + 1]];
            var v2 = meshData.Vertices[meshData.Indices[i + 2]];

            var intersection = TrianglePlaneIntersection(
                v0, v1, v2, sectionPlane);

            if (intersection != null)
            {
                result.IntersectionSegments.Add(intersection);
            }
        }

        // 将交线段连接成闭合轮廓
        result.Outlines = ConnectSegments(result.IntersectionSegments);

        return result;
    }
}

11.7 投影结果收集器

11.7.1 PrjResultCollector

public class PrjResultCollector
{
    /// <summary>
    /// 收集并整理投影结果
    /// </summary>
    public ViewResult Collect(List<ProjectedEntity> projectedEntities)
    {
        var result = new ViewResult
        {
            VisibleLines = new List<LcLine>(),
            HiddenLines = new List<LcLine>(),
            SilhouetteLines = new List<LcLine>()
        };

        foreach (var entity in projectedEntities)
        {
            // 合并重叠线段
            var mergedVisible = MergeOverlappingLines(entity.VisibleLines);
            var mergedHidden = MergeOverlappingLines(entity.HiddenLines);

            result.VisibleLines.AddRange(mergedVisible);
            result.HiddenLines.AddRange(mergedHidden);
            result.SilhouetteLines.AddRange(entity.SilhouetteLines);
        }

        // 去除重复线段
        result.VisibleLines = RemoveDuplicates(result.VisibleLines);
        result.HiddenLines = RemoveDuplicates(result.HiddenLines);

        // 计算包围盒
        var allLines = result.VisibleLines
            .Concat(result.HiddenLines)
            .Concat(result.SilhouetteLines);

        result.Bounds = CalculateBounds(allLines);

        return result;
    }
}

11.8 PolygonExt多边形扩展

11.8.1 多边形操作

PolygonExt.cs(22KB)提供了大量的多边形操作方法,用于支持视图构建中的几何处理:

public static class PolygonExt
{
    /// <summary>
    /// 多边形裁剪(Sutherland-Hodgman算法)
    /// </summary>
    public static List<Point2d> ClipPolygon(
        List<Point2d> polygon, BoundingBox2d clipBox)
    {
        var output = new List<Point2d>(polygon);

        // 依次用四条边裁剪
        output = ClipByEdge(output, clipBox.Min.X, true, true);   // 左
        output = ClipByEdge(output, clipBox.Max.X, true, false);  // 右
        output = ClipByEdge(output, clipBox.Min.Y, false, true);  // 下
        output = ClipByEdge(output, clipBox.Max.Y, false, false); // 上

        return output;
    }

    /// <summary>
    /// 多边形偏移
    /// </summary>
    public static List<Point2d> OffsetPolygon(
        List<Point2d> polygon, double distance)
    {
        var result = new List<Point2d>();
        int n = polygon.Count;

        for (int i = 0; i < n; i++)
        {
            var prev = polygon[(i - 1 + n) % n];
            var curr = polygon[i];
            var next = polygon[(i + 1) % n];

            // 计算两条边的法线方向
            var dir1 = (curr - prev).Normalize();
            var dir2 = (next - curr).Normalize();
            var normal1 = dir1.Perpendicular();
            var normal2 = dir2.Perpendicular();

            // 偏移后的边
            var offsetLine1Start = prev + normal1 * distance;
            var offsetLine1Dir = dir1;
            var offsetLine2Start = curr + normal2 * distance;
            var offsetLine2Dir = dir2;

            // 求交点
            var intersection = LineLineIntersect.Compute(
                offsetLine1Start, offsetLine1Dir,
                offsetLine2Start, offsetLine2Dir
            );

            result.Add(intersection ?? (curr + normal1 * distance));
        }

        return result;
    }

    /// <summary>
    /// 多边形布尔运算(并集)
    /// </summary>
    public static List<List<Point2d>> Union(
        List<Point2d> polygonA, List<Point2d> polygonB)
    {
        // 使用Weiler-Atherton算法
        // ...
        return new List<List<Point2d>>();
    }

    /// <summary>
    /// 多边形三角化
    /// </summary>
    public static List<(int, int, int)> Triangulate(List<Point2d> polygon)
    {
        // 使用耳朵裁剪法(Ear Clipping)
        var triangles = new List<(int, int, int)>();
        var indices = Enumerable.Range(0, polygon.Count).ToList();

        while (indices.Count > 2)
        {
            bool earFound = false;
            for (int i = 0; i < indices.Count; i++)
            {
                var prev = indices[(i - 1 + indices.Count) % indices.Count];
                var curr = indices[i];
                var next = indices[(i + 1) % indices.Count];

                if (IsEar(polygon, indices, prev, curr, next))
                {
                    triangles.Add((prev, curr, next));
                    indices.RemoveAt(i);
                    earFound = true;
                    break;
                }
            }
            if (!earFound) break;
        }

        return triangles;
    }
}

11.9 本章小结

本章详细介绍了LightCAD的视图构建系统。该系统负责将三维模型转换为各种二维投影视图,支持俯视图、立面图、剖面图等多种视图类型。核心的投影处理器实现了三维到二维的坐标变换,隐藏线消除算法确定了从特定视角观察时的线段可见性,B-Rep视图构建器针对实体模型进行了优化处理。视图构建系统是LightCAD中技术难度最高的子系统之一,是工程制图功能的技术基石。


上一章第十章:二维绘图与视口管理

下一章第十二章:输入与交互系统