第十一章:视图构建系统
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中技术难度最高的子系统之一,是工程制图功能的技术基石。
上一章:第十章:二维绘图与视口管理
下一章:第十二章:输入与交互系统