znlgis 博客

GIS开发与技术分享 — GDAL · GeoServer · PostGIS · QGIS · OpenLayers · Cesium · FreeCAD · NPOI

第05章:几何与网格建模

Ara3D.Geometry 是 SDK 的几何内核:网格类型、拓扑、程序化建模、空间查询、包围体与导出。它的一个核心哲学是不可变(immutable)——所有变换都返回新的几何对象,不做原地修改。本章系统讲解网格类型、变换、程序化生成与 AABB 空间查询。

1. 网格类型体系

几何类型都是 [StructLayout(Pack=1)]值类型 struct(由 Plato 生成,落在 Ara3D.Geometry 命名空间),用 IReadOnlyList<Point3D> 存点,用整数索引存面。

1.1 TriangleMesh3D——三角网格

public partial struct TriangleMesh3D : IIndexedGeometry3D<TriangleMesh3D, Integer3>
{
    public readonly IReadOnlyList<Point3D> Points;
    public readonly IReadOnlyList<Integer3> FaceIndices;   // 每个 Integer3 = (A,B,C) 顶点索引

    public TriangleMesh3D(IReadOnlyList<Point3D> points, IReadOnlyList<Integer3> faceIndices);
    public static TriangleMesh3D Create(IReadOnlyList<Point3D> points, IReadOnlyList<Integer3> faceIndices);

    public Bounds3D Bounds { get; }

    // 变换(不可变,返回新网格)
    public TriangleMesh3D Deform(Func<Point3D, Point3D> f);
    public TriangleMesh3D Transform(Transform3D t);
    public TriangleMesh3D Scale(Vector3 v);
    public TriangleMesh3D Scale(Number s);
    public TriangleMesh3D Rotate(Quaternion q);
    public TriangleMesh3D RotateX(Angle a);   // 以及 RotateY / RotateZ
    public TriangleMesh3D Translate(Vector3 v);
    public TriangleMesh3D TranslateX(Number s); // 以及 TranslateY / TranslateZ

    // 派生数据
    public IReadOnlyList<Triangle3D> Triangles { get; }
    public Triangle3D Triangle(Integer3 index);
}

关键点:所有变换都是不可变的——它们返回一个新的 struct,绝不原地修改。这让几何管线天然可组合、可并行、易推理。

1.2 其他网格类型

// 四边形网格
public partial struct QuadMesh3D : IIndexedGeometry3D<QuadMesh3D, Integer4>
{
    public readonly IReadOnlyList<Point3D> Points;
    public readonly IReadOnlyList<Integer4> FaceIndices;   // (A,B,C,D)
}

// 线网格(每个面是一对索引)
public partial struct LineMesh3D : IIndexedGeometry3D<LineMesh3D, Integer2>
{
    public readonly IReadOnlyList<Point3D> Points;
    public readonly IReadOnlyList<Integer2> FaceIndices;   // 索引对
}

// 参数化二维网格(行×列点阵)
public partial struct QuadGrid3D
{
    public IReadOnlyList2D<Point3D> Points { get; }   // 二维点阵
    public bool ConnectU { get; }   // U 方向闭合(圆柱)
    public bool ConnectV { get; }   // V 方向闭合(环面)
    public IReadOnlyList<Integer4> FaceIndices { get; }
}

// 带逐顶点颜色的三角网格
public partial struct ColoredTriangleMesh3D
{
    public readonly TriangleMesh3D Mesh;
    public readonly IReadOnlyList<Vector3> VertexColors;
}

QuadGrid3D 是程序化建模的“中间产物”:先构造一张参数化网格,再 Triangulate()TriangleMesh3DConnectU/ConnectV 控制是否首尾闭合,从而区分平面、圆柱、环面等拓扑。

1.3 几何接口

public interface IPointGeometry3D<TSelf> { IReadOnlyList<Point3D> Points { get; } }
public interface IIndexedGeometry3D<TSelf, TFace> : IPointGeometry3D<TSelf>
{ IReadOnlyList<TFace> FaceIndices { get; } }

public interface ITransformable3D<TSelf> { TSelf Transform(Transform3D t); }
public interface IDeformable3D<TSelf>    { TSelf Deform(Func<Point3D, Point3D> f); }

Transform 是刚性/仿射变换,Deform 是逐点任意变形——后者是实现扭曲、球化、噪声等效果的通用入口。

2. 变换与变形

2.1 变换类型

库把变换细分为多种语义类型:

public struct Transform3D      // 基础变换(包一个 Matrix4x4)
public struct Translation3D    // : 纯平移
public struct Rotation3D       // : 基于四元数的旋转
public struct Scaling3D        // : 非均匀缩放

所有网格类型都实现 Transform(Transform3D t)Deform(Func<Point3D,Point3D> f)

2.2 链式不可变管线

var mesh = PlatonicSolids.TriangulatedCube
    .Scale(new Vector3(2, 1, 1))
    .RotateY(Angle.Degrees(45))
    .Translate(new Vector3(0, 0, 5));

2.3 用 Deform 做自定义变形

Deform 接收“旧点 → 新点”的函数,配合 InverseLerp(把点映射到包围盒内的 [0,1] 参数)可以做出各种效果。例如“扭曲变形器”(来自官方示例):

var bounds = mesh.Bounds;
var twisted = mesh.Deform(p =>
{
    var v = p.InverseLerp(bounds);          // 归一化到 [0,1]^3
    var amount = v.Z;                       // 沿 Z 的参数
    var axisAngle = new AxisAngle(Vector3.UnitZ, (amount * revolutions).Turns);
    return p.Transform(axisAngle);          // 越往上扭得越多
});

3. 程序化几何构造

SurfaceConstructorsAra3D.Geometry/SurfaceConstructors.cs)提供了一组返回不可变几何的程序化操作:

// 拉伸(沿向量把折线扫出面)
public static QuadGrid3D Extrude(this IReadOnlyList<Point3D> points, Vector3 vector, int count = 1);
public static QuadGrid3D Extrude(this IReadOnlyList<Point2D> points, Number height, int count = 1);

// 旋转成型(绕轴旋转,如车床/lathe)
public static QuadGrid3D Revolve(this IReadOnlyList<Point3D> points, Vector3 axis, int count);

// 扫掠(把剖面沿一串变换扫出)
public static QuadGrid3D Sweep(this IReadOnlyList<Point3D> points,
    IReadOnlyList<Transform3D> transforms, bool connectU, bool connectV);

// 转换/三角化
public static QuadMesh3D ToQuadMesh3D(this QuadGrid3D grid);
public static TriangleMesh3D Triangulate(this QuadGrid3D grid);

// 把线网格拉伸成四边网格面
public static QuadMesh3D Extrude(this LineMesh3D lineMesh, Vector3 direction, int count);

// 合并多张网格
public static QuadMesh3D ToQuadMesh(this IEnumerable<QuadGrid3D> grids);

示例——用旋转成型构造一个圆柱

// 在 XZ 平面上采样一个圆作为剖面
var profile = 32.Select(i =>
{
    var a = Angle.FractionOfTurn(i, 32);   // 第 i/32 圈
    return new Point3D(a.Cos, 0, a.Sin);
});

// 绕 Y 轴旋转成型
var cylinderGrid = profile.Revolve(Vector3.UnitY, 32);

// 三角化得到可渲染网格
TriangleMesh3D cylinder = cylinderGrid.Triangulate();

3.1 基本体与曲线(示例项目所用)

官方示例项目 examples/Ara3D.Studio.Examples 大量使用一批高层辅助(随几何库提供,实际名称以你的版本为准):

  • PlatonicSolids.TriangulatedCubePlatonicSolids.Icosahedron 等——现成的柏拉图立体;
  • Primitives.Cylinder(sides, radius, height, segments)——直接生成参数化圆柱;
  • Curves.CircleCurves.Helix(height, revolutions)——参数化曲线,.Sample(n) 采样点、.GetTransforms(n) 生成沿路径的坐标系(frames);
  • Sweep——把剖面沿曲线坐标系扫掠成管、螺旋等。

示例——沿螺旋线扫掠圆形剖面(来自 SweepDemo

var profile = Curves.Circle.RotateX(0.25f.Turns());
var path = Curves.Helix(height: 3, revolutions: 3);
var profilePoints = profile.Sample(16);
var pathFrames = path.GetTransforms(16);
QuadGrid3D tube = profilePoints.Sweep(pathFrames, connectU: true, connectV: false);

3.2 函数式二维网格

FunctionalReadOnlyList2D<T> 用 lambda 直接定义点阵而不预分配数组:

var points = new FunctionalReadOnlyList2D<Point3D>(
    cols + 1, rows + 1,
    (i, j) => new Point3D(i / (float)cols, j / (float)rows, 0));
var grid = new QuadGrid3D(points, ConnectU: false, ConnectV: false);

这在生成大规模参数曲面(如波浪面、地形)时非常省内存。

4. 几何工具方法

GeometryUtil 提供了一批实用扩展:

public static class GeometryUtil
{
    // 集合包围盒
    public static Bounds3D Bounds(this IEnumerable<TriangleMesh3D> meshes);
    public static Bounds3D Bounds(this IEnumerable<QuadMesh3D> meshes);

    // 向量批处理
    public static IReadOnlyList<Vector3> Normalize(this IReadOnlyList<Vector3> vectors);
    public static IReadOnlyList<Vector3> Rotate(this IReadOnlyList<Vector3> self, Vector3 axis, float angle);
    public static IReadOnlyList<Vector3> Transform(this IReadOnlyList<Vector3> self, Matrix4x4 matrix);

    // 2D 线-线相交
    public static bool Intersection(this Line2D l1, Line2D l2, out Vector2 point, float epsilon = 1e-6f);

    // 插值辅助
    public static Number Unlerp(this Number value, Number min, Number max);
    public static Vector3 InverseLerp(this Point3D point, Bounds3D bounds);
    public static Color Lerp(this Color c0, Color c1, float amount);
}

5. 包围体 Bounds3D

Bounds3D 是轴对齐包围盒(AABB):

public partial struct Bounds3D
{
    public readonly Point3D Min;
    public readonly Point3D Max;
    public Vector3 Size { get; }
    public Point3D Center { get; }
    public static Bounds3D Empty { get; }

    public Bounds3D Include(Point3D p);
    public Bounds3D Include(Bounds3D other);
    public Bounds3D Transform(Matrix4x4 m);
}

Bounds3D.Empty 作为起点,逐步 Include 点或子包围盒即可增量求整体包围盒。

6. AABB 树——空间查询

对于“射线求交、范围查询、最近邻”等空间查询,几何库提供了 AabbTreeAra3D.Geometry/AABBTree.cs):

public interface IAabbTreeQuery
{
    bool ShouldVisit(in Bounds3D boundsA, out float priority);
    bool Visit(int index, in Bounds3D bounds);
    bool ShouldStop { get; }
}

public sealed class AabbTree
{
    public AabbTree(IReadOnlyList<Bounds3D> bounds, int maxItemsPerLeaf = 8);
    public int Count { get; }
    public Bounds3D TotalBounds { get; }   // 根包围盒

    // 泛型遍历:TQuery 必须是实现 IAabbTreeQuery 的 struct
    public bool Traverse<TQuery>(ref TQuery query) where TQuery : struct, IAabbTreeQuery;

    public IReadOnlyList<Node> GetAllNodes();
    public Bounds3D GetBounds(int index);
}
  • 构建:沿中心点包围盒最长轴做递归中位数分割;
  • 遍历:按优先级的广度优先,ShouldStop 可提前终止;
  • 性能技巧:查询以 struct 实现 IAabbTreeQuery 并按 ref 传入 Traverse,避免装箱与虚调用开销。
// 为每个网格算包围盒,建树
var meshBounds = model.Meshes.Select(m => m.Bounds).ToList();
var tree = new AabbTree(meshBounds, maxItemsPerLeaf: 8);

// 自定义查询:实现 IAabbTreeQuery 的 struct,再 tree.Traverse(ref query)

7. SDF/体素说明

README 的元包描述里提到 Ara3D.Geometry 含“SDFs、voxels”。在撰写本教程所依据的源码快照里,src/Ara3D.Geometry/ 尚未见到公开的 ISdf / Voxel 等类型(可能位于 WIP 分支或后续版本,或以更高层封装存在)。示例项目中确有 SDF/ 子目录演示相关思路。如果你的版本提供了这些能力,请以实际 API 为准。

8. 本章小结

  • 几何类型是不可变值类型TriangleMesh3DQuadMesh3DLineMesh3DQuadGrid3DColoredTriangleMesh3D
  • 所有变换(Scale/Rotate/Translate/Transform)返回新对象,Deform 支持逐点自定义变形;
  • 程序化建模用 Extrude / Revolve / Sweep 生成 QuadGrid3D,再 Triangulate()
  • 高层辅助(PlatonicSolidsPrimitivesCurvesFunctionalReadOnlyList2D)让参数化建模非常简洁;
  • Bounds3D 提供 AABB,AabbTree 提供高性能空间查询(struct 查询 + ref 遍历)。

下一章我们把网格组织成场景——Ara3D.ModelsModel3D、实例化与 GPU 就绪的渲染缓冲区。


← 上一章 目录 下一章 →