动态生成 3D 可打印的收款码

本文介绍了如何使用C++程序将二维码转化为3D模型,并以STL文件格式保存,适合3D打印。通过ZXing库生成二维码,然后将其3D化,黑色和白色方块具有不同高度,形成凹凸效果。最终,代码实现将每个方块的顶点和三角形面片详细描述,创建出3D收款码模型。

本文介绍了一种制作 3D 收款码的方法。通过本文介绍的 C++ 程序,可以将收款码(本质上是一串二进制数据)转换为 3D 模型(STL 文件),然后就可以用 3D 打印机打印出来了。

当然,要真正拿到属于你的 3D 收款码的实物,你首先需要一台 3D 打印机,不过本文重点是其中的几何算法、程序设计思路。

我已经把这个 C++ 程序开源,有兴趣的可以试试使用这个工具。Github 地址

1、3D 模型概述

三维物体由 3D 模型描述。 3D 模型是一系列相互连接的外包络三角形面片。也就是说,任意一个模型,其基本组成都是三维空间的三角形,这些三角形串接在一起,形成了三维物体的表面。

把 3D 模型放大看,其表面上有:

  • 一系列的点
  • 一系列的棱,每个棱有两个端点
  • 一系列的三角形面片,每个面片有三个棱、三个顶点
  • 每个棱,连接两个面片

因此,只要给定一个顶点坐标的数组 [(x,y,z)],一个三角形面片的数组 [(a,b,c)],就可以描述一个 3D 模型。这里 a、b、c 是整数,是指向顶点数组的索引。

2、STL 格式介绍

有很多中文件格式,可以原来保存 3D 模型数据到文件中。STL 是工业界比较常用的一种。

STL 除了保存顶点坐标、三角形面片,也保存一些元数据(比如:作者、标题)。

STL 有一个特别的地方,它没有独立保存顶点坐标数组,而是在三角形面片中直接使用顶点坐标。下面是一个三角形面片的定义:

facet normal 0.0 0.0 0.0
  outer loop
    vertex -23 -23 0
    vertex 23 -23 0
    vertex 23 -23 4
  endloop
endfacet

3、3D 收款码设计

收款码是一种二维码,而二维码是一个大正方形包含了一系列黑白方块。将二维码 3D 化,通常是将黑白方块赋予不同的高度。

下面是一个黑色块比较矮的凹型 3D 收款码:

 也可以用凸型表示:

上面的模型都是附加了一个方块宽度的外边框。 

4、二维码模型

下面就 3D 收款码实现的关键思路做一些描述。

4.1、制作二维码(ZXing)

首先要生成二维码,通常用 ZXing 这个开源库来做。ZXing 支持多种条形码、二维码,其中二维码有下列 5 种:

  • Aztec
  • DataMatrix
  • MaxiCode
  • PDF417
  • QRCode(常用)

 为了支持多种二维码格式,我们采用 MultiFormatWriter 来生成二维码。

std::vector<std::vector<unsigned char>> bytes;
auto writer = MultiFormatWriter(format).setMargin(0).setEncoding(encoding).setEccLevel(eccLevel);
auto bits = writer.encode(TextUtfEncoding::FromUtf8(text), 0, 0, configs);
for (int i = 0; i < bits.height(); ++i) {
	auto row = bits.row(i);
	bytes.push_back(std::vector<unsigned char>(row.begin(), row.end()));
}

4.2、二维码 3D 化

二维码 3D 化是比较关键的一步,3D 化就是将描述 3D 模型的所有顶点、三角形给出来。

这里所有的表面都是长方形(正方形也是长方形),每一个长方形可以用两个三角形表示。

考察三维模型,有下列长方形:

  • 底面大正方形
  • 侧面长方形(4个)
  • 顶面:
    • 白色块(包括外边框),每个方块一个正方形
    • 黑色块,每个方块一个正方形
    • 黑色块的侧面(仅与白色块相邻的)

点的唯一性

考察三维模型的顶点,有下列顶点:

  • 底面顶点(4个)
  • 顶面:
    • 每个白色方块(4个)
    • 每个黑色方块(8个,其中4个与白色面等高)

这里很多顶点是重复的,比如下面四个方块共用一个顶点 A。

所以,一个方块的左上角,可以是其左上角相邻方块的右下角,或者上边相邻方块的左下角、左边相邻方块的右上角。

完整的顶点重叠关系如下:

方块的顶点左上角相邻方块上边相邻方块左边相邻方块
左上角右下角左下角右上角
右上角右下角
左下角右下角
右下角

用代码实现为:

size_t QRCodeModel::blockPoint(size_t row, size_t col, size_t index)
{
    size_t& p = blocks[row][col].points[index];
    if (p == 0) {
        auto n = index & 3;
        if (n == 0 && row > 0 && col > 0)
            p = blockPoint(row - 1, col - 1, index + 2);
        else if (n == 0 && row > 0)
            p = blockPoint(row - 1, col, index + 3);
        else if (n == 0 && col > 0)
            p = blockPoint(row, col - 1, index + 1);
        else if (n == 1 && (row > 0 || !++col))
            p = blockPoint(row - 1, col, index + 1);
        else if (n == 3 && (col > 0 || !++row))
            p = blockPoint(row, col - 1, index - 1);
        else if (n != 2 || (++row && ++col))
            p = mesh.addVerticals({ { col * blockSize - radiusX, row * blockSize - radiusY, (index >= 4) == concave ? thickness : thickness / 2 } });
    }
    return p;
}

这里 index 取值范围是 0~7,白色块的等高面的顶点是 4~7,黑色块的等高面的顶点是 0~3。对于凹凸型,黑白色块的高度是不一样的。

黑白方块的面片拆分

黑色方块比白色方块多了侧面,所以要复杂一些,代码实现如下:

void QRCodeModel::generateBlackBlock(size_t row, size_t col)
{
    bool l = blocks[row][col - 1].group == 1; // if left neighbour is black
    bool r = blocks[row][col + 1].group == 1;
    bool t = blocks[row - 1][col].group == 1;
    bool b = blocks[row + 1][col].group == 1;
    size_t points[] = {
        blockPoint(row, col, 0), blockPoint(row, col, 1), blockPoint(row, col, 2), blockPoint(row, col, 3),
        (l && t) ? 0 : blockPoint(row, col, 4),
        (r && t) ? 0 : blockPoint(row, col, 5),
        (r && b) ? 0 : blockPoint(row, col, 6),
        (l && b) ? 0 : blockPoint(row, col, 7),
    };
    mesh.addTriangles({
        {points[0], points[2], points[3]},
        {points[0], points[1], points[2]},
        });
    if (!l)
        mesh.addTriangles({
            {points[0], points[3], points[7]},
            {points[0], points[7], points[4]},
            });
    if (!t)
        mesh.addTriangles({
            {points[0], points[5], points[1]},
            {points[0], points[4], points[5]},
            });
    if (!r)
        mesh.addTriangles({
            {points[1], points[5], points[6]},
            {points[1], points[6], points[2]},
            });
    if (!b)
        mesh.addTriangles({
            {points[3], points[2], points[6]},
            {points[3], points[6], points[7]},
            });
}

5、图标嵌入

6、文字嵌入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值