平面上的三维空间#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 站 。

相关推荐
LinXunFeng40 分钟前
Flutter - GetX Helper 助你规范应用 tag
flutter·github·visual studio code
androidwork1 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·java·kotlin·androidx
每次的天空1 小时前
Android第十三次面试总结基础
android·面试·职场和发展
wu_android1 小时前
Android 相对布局管理器(RelativeLayout)
android
李斯维3 小时前
循序渐进 Android Binder(二):传递自定义对象和 AIDL 回调
android·java·android studio
androidwork3 小时前
OkHttp 3.0源码解析:从设计理念到核心实现
android·java·okhttp·kotlin
像风一样自由4 小时前
【001】frida API分类 总览
android·frida
casual_clover4 小时前
Android 之 kotlin 语言学习笔记四(Android KTX)
android·学习·kotlin
移动开发者1号5 小时前
Android 大文件分块上传实战:突破表单数据限制的完整方案
android·java·kotlin
移动开发者1号6 小时前
单线程模型中消息机制解析
android·kotlin