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

相关推荐
子兮曰1 小时前
深入 HTML-in-Canvas:当 Canvas 学会了渲染 DOM,前端图形生态要变天了
前端·javascript·canvas
Lanren的编程日记1 小时前
Flutter 鸿蒙应用数据版本管理实战:版本记录+版本回退+版本对比,实现全链路数据版本控制
flutter·华为·harmonyos
饭小猿人4 小时前
Android 腾讯X5WebView如何禁止系统自带剪切板和自定义剪切板视图
android·java
_李小白5 小时前
【android opencv学习笔记】Day 8: remap(像素位置重映射)
android·opencv·学习
美狐美颜SDK开放平台5 小时前
多场景美颜SDK解决方案:直播APP(iOS/安卓)开发接入详解
android·人工智能·ios·音视频·美颜sdk·第三方美颜sdk·短视频美颜sdk
嗷o嗷o5 小时前
Android BLE 里,MTU、分包和长数据发送到底该怎么处理
android
Gary Studio7 小时前
Android AIDL HAL工程结构示例
android
y = xⁿ7 小时前
MySQL八股知识合集
android·mysql·adb
MonkeyKing8 小时前
Flutter列表性能极致优化:从卡顿到丝滑
flutter·dart
IntMainJhy8 小时前
「Flutter三方库sqflite的鸿蒙化适配与实战指南:从入门到踩坑的本地数据库开发全记录」
数据库·flutter·华为·信息可视化·数据库开发·harmonyos