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

相关推荐
北执南念32 分钟前
IDEA回滚代码操作
android·java·intellij-idea
蓝桉柒71 小时前
PHP分页显示数据,在phpMyadmin中添加数据
android·ide·android studio
美狐美颜sdk1 小时前
什么是美颜SDK?美颜SDK安卓与iOS端开发指南
android·人工智能·ios·音视频·美颜sdk·直播美颜sdk
n33(NK)2 小时前
MySQL 窗口函数入门到精通
android·数据库·mysql
撸码到无法自拔3 小时前
android Kotlin ,internal class , data class, class的区别
android·java·开发语言·kotlin
jcsx4 小时前
【mysql】常用命令
android·数据库·mysql
青年夏日科技工作者4 小时前
Android WebView加载h5打开麦克风与摄像头的权限问题
android·数码相机·harmonyos
云小逸4 小时前
【C++核心技术深度解析:从继承多态到STL容器 】
android·java·c++
努力学习的小廉5 小时前
深入了解linux系统—— 进程地址空间
android·linux·服务器
diaostar8 小时前
Android OKHttp原理简单说明
android·okhttp