平面上的三维空间#05 | 几何形体

在上一篇,我们了解了如何绘制三维空间中的 三角形。本文将进一步基于三角形绘制更为复杂的形体。


1. 绘制正多边形

下面是一个正八边形,它可以看成是由 8 个共顶点的三角形构成的,橙色点时绘制三角形所需的顶点。想绘制出正多边形, 本质问题就在于:

如何计算这些橙色点在坐标系中的坐标。

记,八边形中分割的等边三角形顶角为 θ ,腰长为 r ,其实很容易计算出:

第 2 个点坐标: (r * cos(θ), r * sin(θ))

第 3 个点坐标: (r * cos(2θ), r * sin(2θ))

第 4 个点坐标: (r * cos(3θ), r * sin(3θ))

... 第 n 个点坐标: (r * cos((n-1)*θ), r * sin((n-1)*θ))

于是可以根据分析的公式,通过代码来创建符合正多边形的顶点列表:

dart 复制代码
class Shape3d {
  List<Point3D> circle(double r, int splitCount) {
    Point3D center = Point3D(0, 0, 0);
    List<Point3D> vertexes = [center];

    double thta = 2 * pi / splitCount;
    int count = splitCount + 2;
    for (int n = 1; n < count; n++) {
      double rad = (n - 1) * thta;
      double x = r * cos(rad);
      double y = r * sin(rad);
      double z = 0;
      vertexes.add(Point3D(x, y, z));
    }
    return vertexes;
  }
}

根据上一篇中使用顶点列表绘制三角形的方式,就可以在坐标轴上得到如下的正八边形图案:

dart 复制代码
List<Point3D> points3d = Shape3d().circle(4, 8);
Path path = buildPathByPoints3d(points3d, type: DrawArrays.triangleFan);
canvas.drawPath(path, paint);

2. 绘制圆形与圆锥

当正多边形的边数越来越大,将会近似于一个圆形。如下所示,通过输入框控制边数,就可以动态地查看边数逐渐增加的效果:

代码中为画板提供变量即可,数值由输入框控制,输入框内容改变时通知画板重绘:

dart 复制代码
int sideCount = present.count;
List<Point3D> points3d = Shape3d().circle(4, sideCount);
Path path = buildPathByPoints3d(points3d, type: DrawArrays.triangleFan);
canvas.drawPath(path, paint);

我们还可以做一些更有趣的事,比如将圆周上的点向上移动,顶点不变,就可以得到一个圆锥。如下所示:

可以在 Shape3d 中封装一个 cone 方法绘制圆锥,可以传入 z 轴坐标。通过旋转空间,可以

dart 复制代码
List<Point3D> cone(double r, int splitCount, {double z = 0}) {
  Point3D center = Point3D(0, 0, 0);
  List<Point3D> vertexes = [center];
  double thta = 2 * pi / splitCount;
  int count = splitCount + 2;
  for (int n = 1; n < count; n++) {
    double rad = (n - 1) * thta;
    double x = r * cos(rad);
    double y = r * sin(rad);
    vertexes.add(Point3D(x, y, z));
  }
  return vertexes;
}

3. 三维点线绘制模式

在 OpenGL 标准中,绘制三维点集除了三角形之外,还有点和线。这里通过 螺旋线 介绍一下另外几种点集的绘制方式:

dart 复制代码
enum DrawArrays {
  points, // 点
  lines, // 独立线段
  lineStrip, // 连续折线
  lineLoop, // 闭合折线
  triangles, // 独立三角形
  triangleStrip, // 三角形带
  triangleFan, // 扇形
}

上面螺旋线折线在绘制时用的 lineStrip 模式,点集将以此连接成为折线。绘制是时只需将 path 移到第一点,然后遍历剩余点,通过 lineTo 连接即可:

dart 复制代码
Path _buildLineStripPath(List<Offset> points) {
  Path path = Path();
  if (points.isEmpty) return path;
  path.moveTo(points[0].dx, points[0].dy);
  for (int i = 1; i < points.length; i++) {
    path.lineTo(points[i].dx, points[i].dy);
  }
  return path;
}

如下所示 lines 模式的每两个顶点组成一条独立的线段。绘制时遍历点集列表,两次处理两个点,形成两点之间的线段路径:

dart 复制代码
Path _buildLinesPath(List<Offset> points) {
  Path path = Path();
  for (int i = 0; i + 1 < points.length; i += 2) {
    path
      ..moveTo(points[i].dx, points[i].dy)
      ..lineTo(points[i + 1].dx, points[i + 1].dy);
  }
  return path;
}

lineLoop 模式绘制闭合的曲线,只需要在 lineStrip 基础上增加连接起点的操作即可:

dart 复制代码
Path _buildLineLoopPath(List<Offset> points) {
  Path path = _buildLineStripPath(points);
  if (points.length > 2) {
    path.close(); // 回到起点
  }
  return path;
}

points 模式绘制点,这里遍历点集,通过 addOval 添加小圆点路径:

dart 复制代码
Path _buildPointsPath(List<Offset> points) {
  Path path = Path();
  for (Offset p in points) {
    path.addOval(Rect.fromCircle(center: p, radius: 1)); // 小圆点
  }
  return path;
}

4. 螺旋线的绘制

另外四种绘制方式介绍完毕,现在看一下螺旋线的绘制。在数学中,可以通过参数方程给出螺旋线的表达式:

c 复制代码
x(t) = r⋅cos(2πnt)
y(t) = r⋅sin(2πnt)
z(t) = h⋅t

其中:

  • t∈[0,1],表示螺旋线在起点到终点之间的插值;
  • r 是半径,h 是高度,n 是圈数。

于是在代码层面,可以基于此来收集符合螺旋线规律的点集合:

参数 说明
radius 螺旋的半径(即绕 z 轴旋转的圆的半径)
height 总高度(z 方向的位移)
turns 旋转的圈数(每圈 2π 弧度)
splitCount 将螺旋细分为多少段(决定曲线的精细程度)
dart 复制代码
List<Point3D> helicoid(double radius, double height, int turns, int splitCount) {
  List<Point3D> vertexes = [];
  double totalAngle = 2 * pi * turns;
  for (int i = 0; i <= splitCount; i++) {
    double t = i / splitCount;
    double angle = t * totalAngle;
    double x = radius * cos(angle);
    double y = radius * sin(angle);
    double z = height * t;
    vertexes.add(Point3D(x, y, z));
  }
  return vertexes;
}

5. 尾声

其实,任何有规则的图形,我们只要直到构成它的规律。进行采样,就可以通过收集点的方式,从而将它在三维空间中表达出来。比如:

空间中的贝塞尔曲线:

球体

更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。

相关推荐
stevenzqzq20 小时前
android启动初始化和注入理解3
android
LawrenceLan1 天前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
一豆羹1 天前
macOS 环境下 ADB 无线调试连接失败、Protocol Fault 及端口占用的深度排查
flutter
行者961 天前
OpenHarmony上Flutter粒子效果组件的深度适配与实践
flutter·交互·harmonyos·鸿蒙
城东米粉儿1 天前
compose 状态提升 笔记
android
粤M温同学1 天前
Android 实现沉浸式状态栏
android
ljt27249606611 天前
Compose笔记(六十八)--MutableStateFlow
android·笔记·android jetpack
stevenzqzq1 天前
Android Studio 断点调试核心技巧总结
android·ide·android studio
aqi001 天前
FFmpeg开发笔记(九十八)基于FFmpeg的跨平台图形用户界面LosslessCut
android·ffmpeg·kotlin·音视频·直播·流媒体