リスペクト : https://qiita.com/3S_Laboo/items/660883a0184dabaea65b
おしゃれなカフェで...
あなたは今おしゃれなカフェで作業をしている。
作業にひと段落が付き、背筋をぐっと伸ばしあたりを見渡す。
すると、あなたの近くに、コーヒーとMacを持った人物が座っていることに気付く。
マナー違反であることは承知しているが、誘惑に負けてMacの画面をのぞき込む。
なにやらターミナルが忙しく動いている。
あなたはエンジニア仲間を見つけ喜びを感じる。しかし一方で、ある違和感を覚える。
その人物はプログラミングをしているのではなく、プログラミングしている風の画面を出し自己肯定感を上げていたのだ。
あなたは仲間を見つけた喜びを裏切られた気分になり、次第に感情は苛立ちへと変化していく。
そしてあなたは自身の作業のことを忘れ、プログラミングしている風の人物に真のエンジニアリングを見せつけると決意する。
しかし相手は日頃からQiitaを見ているであろう人物。ターミナルに大量の文字列を表示しているものを見せつけたとて、動じるとは考えられない。
そこであなたは気付く。文字以外を表示すればよいことに。
描画準備
何を描画するにしろ、まずは描画するための準備が必要だ。
あなたはANSIエスケープシーケンスを駆使し、様々な色の空白をピクセルのように扱うことで描画する方法に取り掛かる。
#include <signal.h>
#define NOMINMAX
#include <windows.h>
volatile sig_atomic_t _FLAG = 0;
static void close(int signal) { _FLAG = 1; };
int main(int argc, char* argv[]) {
signal(SIGINT, close);
DWORD l_mode;
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleMode(hStdout, &l_mode);
SetConsoleMode(hStdout, l_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
CONSOLE_FONT_INFOEX cfi;
cfi.cbSize = sizeof(cfi);
cfi.nFont = 0;
cfi.dwFontSize.X = 1;
cfi.dwFontSize.Y = 1;
cfi.FontFamily = FF_DONTCARE;
cfi.FontWeight = FW_NORMAL;
wcscpy_s(cfi.FaceName, L"Consolas");
SetCurrentConsoleFontEx(hStdout, FALSE, &cfi);
size_t width = 300, height = 300;
COORD dwSize = { static_cast<SHORT>(width) + 1, static_cast<SHORT>(height) + 1 };
SetConsoleScreenBufferSize(hStdout, dwSize);
SMALL_RECT consoleWindow = { 0, 0, dwSize.X - 1, dwSize.Y - 1 };
SetConsoleWindowInfo(hStdout, TRUE, &consoleWindow);
//----------描画ループ-------------
for (size_t count = 0; !_FLAG; count++) {
}
}
#include <string>
std::string colorSpace(const auto col) {
return "\033[48;2;" +
std::to_string(col[0]) + ";" +
std::to_string(col[1]) + ";" +
std::to_string(col[2]) + "m \033[0m";
}
ここであなたは気付く。環境依存のコードすぎないか、と。
好きなものを描く
準備はできた。あとは描画するものを画像のように二次元の配列で表現すれば完成だ。
//----------描画ループ-------------
for (size_t count = 0; !_FLAG; count++) {
std::string buf{};
for (size_t i = 0; i < height; i++) {
for (size_t t = 0; t < width; t++) {
buf += colorSpace(color[i][t]);
}
buf += "\n";
}
std::cout << "\033[H";
std::cout << buf;
}
深く息を吸う。まずは相手への親愛と敬意をこめてハートを描画することに決める。
ハートの方程式を調べ、点群を生成、拙い手段で座標をターミナル中央に調整する。
std::vector<glm::vec2> heart(const size_t n) {
std::vector<glm::vec2> pts(n);
for (size_t i = 0; i < n; i++) {
double t = (double)i / n * std::numbers::pi * 2.0;
double x = 16 * pow(sin(t), 3);
double y = 13 * cos(t) - 5 * cos(2 * t) - 2 * cos(3 * t) - cos(4 * t);
pts[i] = { x / 16.0, -((y + 2.5) / 14.5) };
}
return pts;
}
void drawHeart(const size_t n, const size_t count, const size_t width, const size_t height) {
std::vector<std::vector<glm::ivec3>> color(height, std::vector<glm::ivec3>(width, {0, 0, 0}));
std::vector<glm::vec2> pts = heart(n);
auto rot = glm::toMat4(glm::quat(glm::vec3(0.0, count * 0.1, 0.0)));
for (auto& p : pts) {
auto v = rot * glm::vec4(p, 0.0, 1.0);
v *= (width / 2.0) * 0.9;
v += (width / 2.0);
if (v.y < 0 || v.y >= height || v.x < 0 || v.x >= width) continue;
color[(size_t)v.y][(size_t)v.x] = { 255, 255, 255 };
}
}
まだまだ満足していないあなたは、カオスとフラクタルの世界へと踏み込む。
ハートと同様の手段でThomas' cyclically symmetric attractor点群を生成し描く。
さらにはプログラマなら皆大好きマトリックス風の画面を0、1の点群を生成し描く。
ここであなたは気付く。可能性が無限大であることに。
コンピュータグラフィックスの知識をもってすれば、三角形と線分の交差判定によりスタンフォードバニーを描画するだけにとどまらず、ターミナル上で物理ベースレンダリングも夢ではないのだ。
終わりに
あなたは次々とターミナルに描画をする。あなたを止められる人はどこにもいない。
画面に広がる混沌を見つめ恍惚とした表情を浮かべるあなた。
そしてあなたは気付く。なんだこれ、と。