平面上的三维空间#04 | 万物之母 - 三角形

上一篇我们介绍了三维空间到二维平面的 轴测投影 的原理,今天将进一步探索三维空间中的几何图形。


1. 绘制三角形

在第一篇就介绍了绘制空间中 坐标点 的方式, 正所谓: 点动成先,线动成体。有了一点,理论上就可以绘制万物。现在思考一下,如下所示有三个橙色的点,如何在 XOY 平面上绘制三角形:

dart 复制代码
List<Point3D> points = [
  Point3D(-4, 4, 0),
  Point3D(-4, 1, 0),
  Point3D(-1, 1, 0),
];

可以通过根据顶点形成 Path , 绘制路径即可形成符合三维空间的图形。如下所示,三角形在旋转时会保持三维空间的特性:

dart 复制代码
Path path = Path();
List<Offset> point2ds = points.map(project).toList();
path.moveTo(point2ds[0].dx, point2ds[0].dy);
path.lineTo(point2ds[1].dx, point2ds[1].dy);
path.lineTo(point2ds[2].dx, point2ds[2].dy);
path.lineTo(point2ds[0].dx, point2ds[0].dy);
Paint paint = Paint()
  ..color = Colors.cyanAccent
  ..strokeWidth = 2
  ..style = PaintingStyle.stroke;
canvas.drawPath(path, paint);

2. 万物皆为三角形

了解过三维建模或者OpenGL 的朋友可能知道: 所有的体和面都是由三角形拼组而成。既然这里可以画出三维空间中的三角形。那么我有一个大胆的想法,可以基于此绘制复杂的三维形体。

首先要做的一件事是: 封装三角形 的路径绘制。在此之前,先了解一下 OpenGL 标准中三角形的三种绘制方式:

GL_TRIANGLES: 独立三角形

  • 每三个顶点被当作一个三角形。

GL_TRIANGLE_STRIP: 三角形带

  • 每添加一个新顶点,都会创建一个新的三角形,且每次都是通过前两个顶点和当前的新顶点来定义一个三角形

GL_TRIANGLE_FAN: 扇形

第一个顶点是三角形扇形的中心,剩下的每个相邻顶点与中心一起组成一个三角形


3. 封装绘制三角形的方法

现在我们基于 Canvas 中的路径,封装三角形路径的形成方式。三种方式通过 DrawArrays 枚举区分:

dart 复制代码
enum DrawArrays {
  triangles, // 独立三角形
  triangleStrip, // 三角形带
  triangleFan, // 扇形
}

封装 buildPathByPoints3d 方法,传入点列表和绘制三角形的方式:

dart 复制代码
Path buildPathByPoints3d(
  List<Point3D> points3d, {
  DrawArrays type = DrawArrays.triangles,
}) {
  Path path = Path();
  if (points3d.length == 1) path;
  List<Offset> points = points3d.map((project)).toList();
  return switch (type) {
    DrawArrays.triangles => _buildTrianglesPath(points),
    DrawArrays.triangleStrip => _buildStripPath(points),
    DrawArrays.triangleFan => _buildFanPath(points),
  };
}

然后根据三种不同形式的定义,进行具体实现。OpenGL 中之所要分三种绘制三角形的方式,是因为要适应不同场景:

triangles : 可以灵活绘制任何三角形,但是定点数永远是 三角形数*3 ,无法复用顶点。

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

triangleStrip : 带状物体,如地形、纸带、绳索等连续结构。相邻三角形顶点可以复用。

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

triangleFan : 圆形、扇形、雷达图等放射状结构。三角形共享中心顶点。

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

绘制时只需要指定三维空间中的坐标列表即可,比如下面是八个点通过 triangleFan 模式绘制的三角形列表:

dart 复制代码
List<Point3D> points3d = [
  Point3D(4.00, 0.00, 0),
  Point3D(2.83, 2.83, 0),
  Point3D(0.00, 4.00, 0),
  Point3D(-2.83, 2.83, 0),
  Point3D(-4.00, 0.00, 0),
  Point3D(-2.83, -2.83, 0),
  Point3D(0.00, -4.00, 0),
  Point3D(2.83, -2.83, 0),
];

Path path = buildPathByPoints3d(points3d, type: DrawArrays.triangleFan);
canvas.drawPath(path, paint);

4. 项目与源码

后续 《Flutter 伪 3D 绘制系列》 的项目将归属到 toly_game 中,大家可以在 github 开源仓库中得到可以运行的源码。如下所示,TolyGameBox 中已经集成 3D 世界 :

打开后就是可以看到该系列的效果,以后也会在这里继续玩耍 3D 世界。

伪 3d 绘制的代码也是单独分库,在项目中的 toly3d 模块中:


5. 尾声

通过这系列,仅希望探索 2d 绘制 3d 效果的可行性,并基于此介绍一些三维空间的特点。这和真正的 OpenGL 三维绘制是不可同日而语的。目前来看,不依赖与任何三维引擎,可以做到目前的效果,我已经非常满意了。下一篇,将会基于三角形,绘制空间中的其他图形,比如圆、环、多边形、甚至是曲面、三维形体。敬请期待 ~

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

相关推荐
_祝你今天愉快8 分钟前
深入剖析Java中ThreadLocal原理
android
张力尹1 小时前
谈谈 kotlin 和 java 中的锁!你是不是在协程中使用 synchronized?
android
流浪汉kylin1 小时前
Android 斜切图片
android
PuddingSama2 小时前
Android 视图转换工具 Matrix
android·前端·面试
亦黑迷失2 小时前
实现 canvas 交互(元素的事件处理)
前端·typescript·canvas
RichardLai882 小时前
[Flutter学习之Dart基础] - 控制语句
android·flutter
archko2 小时前
compose map 源码解析
android
大熊的瓜地2 小时前
从零开始写android 的智能指针
android
甜辣小悦羊3 小时前
Android Studio 的安装教程
android·ide·android studio
louisgeek3 小时前
Android Intent
android