CによるカオスCG(川上博・上田哲史, サイエンス社, 1994)の勉強用ノートです。
古い本ですが、多くの大学図書館の蔵書となっているほか、出版元から購入もできます。
この本に掲載されているコードはBorland社によるTurbo C++ ver1.0またはTurbo C ver2.0で動作するように書かれています。ここで使われているgraphics.libなるライブラリはBGI(Borland Graphical Interface)に付属するもので、現在の環境ではもはや一般的なものではありません。
お手軽なグラフィックス環境としてJavaベースのProcessingが普及していますが、ここではあえて表題通りのC言語で、グラフィックスを使う手段としてGLUTを使って再現を試みました。
環境
- Windows 10 Pro 64bit
- OpenGL ver4.6.0
- Visual Studio Community 2019
- 「Desktop development with C++(C++によるデスクトップ開発)」ワークロードをインストール済
- GLUT : The OpenGL Utility Toolkitを設定済
詳しくはこちらの記事をどうぞ。
コード例 Prog. 1.1
元コード
本より引用。コメントは文中の解説を参考に付記したもの。
/* Prog. 1.1 */
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <graphics.h>
void main()
{
/* グラフィックスのためのドライバとモードの設定. PC-9801を16色で使用する. */
int gdriver = PC98, gmode = PC98C16;
/* 変数の初期化. 初期値(x0,y0)の設定. */
unsigned long count = 0;
int i = 0, col = 1, u0 = 320, v0 = 200, slx = 8, sly = 8, u, v;
double x, y, x0 = 4.0, y0 = 0.0;
/* グラフィックスの初期化. "A:\\TC\\BGI\\"はTurbo-Cのグラフィックスデバイスドライバのあるディレクトリ名を指定する. */
initgraph(&gdriver, &gmode, "A:\\TC\\BGI\\");
while (1){
/* 式(1.1)の計算. */
x = y0 + 0.2 * x0 + 5.0 * x0 / (1.0 + x0 * x0);
y = -x0;
/* 直後にxとyの値を保存し, */
x0 = x; y0 = y;
/* スケール変換する. このとき, ディスプレイ上の座標系を考慮してyの値を負にしている. */
u = x * slx + u0; v = v0 - y * sly;
/* 点を描画, カウンタを1増やす. */
putpixel(u, v, col);
/* 一万点毎に配色を変え, 描画点数を表示する. */
if (!(++count % 10000)){
col=(++i % 15) + 1; printf("%lu\n",count);
}
/* 任意のキーが押されていたらループを抜け, 終了する. */
if (kbhit()) break;
}
/* テキスト・カーソルを表示させる. */
printf("%c[>5l", 0x1b);
}
conio.hはkbhit()を呼び出すためにインクルードしています。
graphics.hが問題のライブラリです。initgraphでグラフィックスを初期化し、putpixelで指定した座標・色の点を描画しています。
BGIの座標系
原点は画面の左上の隅、x軸は右が正、y軸は下が正。
四隅の座標は、画面左上(0, 0), 右上(640, 0), 右下(640, 400), 左下(0, 400)。
描画する前に「スケール変換」を行っていますが、これは見た目で画面中央に原点が来るように、y軸の上が正となるようにし、計算結果が画面内に収まるようにする作業です。
BGIの配色
BGIのカラーパレットを調べたところ、大体こんなところかなと推測できました。
16色しか使えないので、各色に番号が振られていて、その番号で色を指定できるのですね。
| 色番号 | 色名称 | R | G | B |
|:--|:--|:--|:--|:--|:--|
| 0 | BLACK | 0 | 0 | 0 |
| 1 | BLUE | 0 | 0 | 2/3 |
| 2 | GREEN | 0 | 2/3 | 0 |
| 3 | CYAN | 0 | 2/3 | 2/3 |
| 4 | RED | 2/3 | 0 | 0 |
| 5 | MAGENTA | 2/3 | 0 | 2/3 |
| 6 | BROWN | 2/3 | 0 | 0 |
| 7 | LIGHTGRAY | 2/3 | 2/3 | 2/3 |
| 8 | DARKGRAY | 1/3 | 1/3 | 1/3 |
| 9 | LIGHTBLUE | 0 | 0 | 1 |
| 10 | LIGHTGREEN | 0 | 1 | 0 |
| 11 | LIGHTCYAN | 0 | 1 | 1 |
| 12 | LIGHTRED | 1 | 0 | 0 |
| 13 | LIGHTMAGENTA | 1 | 0 | 1 |
| 14 | YELLOW | 1 | 1 | 0 |
| 15 | WHITE | 1 | 1 | 1 |
GLUT仕様に書き換え
GLUTでは、原点は画面中央、x軸は右が正、y軸は上が正。
四隅の座標は、画面左上(-1, 1), 右上(1, 1), 右下(1, -1), 左下(-1, -1)。
配色データを構造体で書きました。
/* Prog. 1.1 (GLUT) */
#include <stdio.h>
#include <GL/glut.h>
void display(void);
void idle(void);
/* PC-9801を16色で使用したときの配色を再現する. */
struct color_rgb{
char name[13];
double red;
double green;
double blue;
};
struct color_rgb bgicolor[16] = {
{"BLACK", 0, 0, 0 },
{"BLUE", 0, 0, 2.0/3 },
{"GREEN", 0, 2.0/3, 0 },
{"CYAN", 0, 2.0/3, 2.0/3 },
{"RED", 2.0/3, 0, 0 },
{"MAGENTA", 2.0/3, 0, 2.0/3 },
{"BROWN", 2.0/3, 1.0/3, 0 },
{"LIGHTGRAY", 2.0/3, 2.0/3, 2.0/3 },
{"DARKGRAY", 1.0/3, 1.0/3, 1.0/3 },
{"LIGHTBLUE", 1.0/3, 1.0/3, 1 },
{"LIGHTGREEN", 1.0/3, 1, 1.0/3 },
{"LIGHTCYAN", 1.0/3, 1, 1 },
{"LIGHTRED", 1, 1.0/3, 1.0/3 },
{"LIGHTMAGENTA", 1, 1.0/3, 1 },
{"YELLOW", 1, 1, 1.0/3 },
{"WHITE", 1, 1, 1 }
};
/* 変数の初期化. 初期値(x0,y0)の設定. */
unsigned long count = 0;
int i = 0, col = 1;
double u0 = 0, v0 = 0, slx = 1.0/20, sly = 1.0/20, u, v;
double x, y, x0 = 4.0, y0 = 0.0;
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitWindowPosition(100, 100);
glutInitWindowSize(1000, 1000);
glutInitDisplayMode(GLUT_RGBA);
glutCreateWindow("Prog. 1.1");
/* 描画用に呼び出す関数を指定. */
glutDisplayFunc(display);
/* アイドル状態のときに呼び出す関数を指定. オリジナルのwhile(1){処理}の代用. */
glutIdleFunc(idle);
/* ウィンドウが破棄されたらループを抜け, 終了する. */
glutMainLoop();
return 0;
}
void display(void){
/* 式(1.1)の計算. */
x = y0 + 0.2 * x0 + 5.0 * x0 / (1.0 + x0 * x0);
y = -x0;
/* 直後にxとyの値を保存し, */
x0 = x; y0 = y;
/* スケール変換する. ディスプレイ上の座標系がオリジナルと異なることに注意する. */
u = x * slx + u0; v = y * sly + v0;
/* 点を描画, カウンタを1増やす. */
glColor3d(bgicolor[col].red, bgicolor[col].green, bgicolor[col].blue);
glBegin(GL_POINTS);
glVertex2d(u, v);
glEnd();
/* 一万点毎に配色を変え, 描画点数を表示する. */
if (!(++count % 10000)){
col=(++i % 15) + 1; printf("%lu\n",count);
}
glFlush();
}
void idle(void){
/* glutDisplayFunc()を実行する. */
glutPostRedisplay();
}
実行結果
おおー、きもちわる 美しい!
同様に
1.1と同様に1.2と1.3を書き換えたコードを載せておきます。
元のコードは本を参照してください。
Prog. 1.2
/* Prog. 1.2 (GLUT) */
#include <stdio.h>
#include <GL/glut.h>
void display(void);
void idle(void);
struct color_rgb{
char name[13];
double r;
double g;
double b;
};
struct color_rgb bgicolor[16] = {
{"BLACK", 0, 0, 0 },
{"BLUE", 0, 0, 2.0/3 },
{"GREEN", 0, 2.0/3, 0 },
{"CYAN", 0, 2.0/3, 2.0/3 },
{"RED", 2.0/3, 0, 0 },
{"MAGENTA", 2.0/3, 0, 2.0/3 },
{"BROWN", 2.0/3, 1.0/3, 0 },
{"LIGHTGRAY", 2.0/3, 2.0/3, 2.0/3 },
{"DARKGRAY", 1.0/3, 1.0/3, 1.0/3 },
{"LIGHTBLUE", 1.0/3, 1.0/3, 1 },
{"LIGHTGREEN", 1.0/3, 1, 1.0/3 },
{"LIGHTCYAN", 1.0/3, 1, 1 },
{"LIGHTRED", 1, 1.0/3, 1.0/3 },
{"LIGHTMAGENTA", 1, 1.0/3, 1 },
{"YELLOW", 1, 1, 1.0/3 },
{"WHITE", 1, 1, 1 }
};
unsigned long count = 0;
int i = 0, col = 1;
double u0 = 0, v0 = 0, slx = 1.0/10, sly = 1.0/10, u, v;
double x, y, x0 = 0.5, y0 = 0.0;
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitWindowPosition(100, 100);
glutInitWindowSize(1000, 1000);
glutInitDisplayMode(GLUT_RGBA);
glutCreateWindow("Prog. 1.2");
glutDisplayFunc(display);
glutIdleFunc(idle);
glutMainLoop();
return 0;
}
void display(void){
x = y0 - 0.97 * x0 - 3.0 + 4.0 / (1.0 + x0 * x0);
y = -0.98 * x0;
x0 = x; y0 = y;
u = x * slx + u0;
v = y0 * sly + v0;
glColor3d(bgicolor[col].r, bgicolor[col].g, bgicolor[col].b);
glBegin(GL_POINTS);
glVertex2d(u, v);
glEnd();
if (!(++count % 10000)){
col=(++i % 15) + 1; printf("%lu\n",count);
}
glFlush();
}
void idle(void){
glutPostRedisplay();
}
Prog. 1.3
/* Prog. 1.3 (GLUT) */
#include <math.h>
#include <GL/glut.h>
void display(void);
struct color_rgb{
char name[13];
double r;
double g;
double b;
};
struct color_rgb bgicolor[16] = {
{"BLACK", 0, 0, 0 },
{"BLUE", 0, 0, 2.0/3 },
{"GREEN", 0, 2.0/3, 0 },
{"CYAN", 0, 2.0/3, 2.0/3 },
{"RED", 2.0/3, 0, 0 },
{"MAGENTA", 2.0/3, 0, 2.0/3 },
{"BROWN", 2.0/3, 1.0/3, 0 },
{"LIGHTGRAY", 2.0/3, 2.0/3, 2.0/3 },
{"DARKGRAY", 1.0/3, 1.0/3, 1.0/3 },
{"LIGHTBLUE", 1.0/3, 1.0/3, 1 },
{"LIGHTGREEN", 1.0/3, 1, 1.0/3 },
{"LIGHTCYAN", 1.0/3, 1, 1 },
{"LIGHTRED", 1, 1.0/3, 1.0/3 },
{"LIGHTMAGENTA", 1, 1.0/3, 1 },
{"YELLOW", 1, 1, 1.0/3 },
{"WHITE", 1, 1, 1 }
};
int i = 0, col = 3;
double u0 = - 0.85 * 2.0/2.7, v0 = 0;
double slx = 2.0/2.7, sly = 1.0/4, u, v;
int flag = 0;
double x = 0.0, y, a, amin = -0.5, amax = 2.2;
int main(int argc, char *argv[])
{
glutInit(&argc, argv);
glutInitWindowPosition(100, 100);
glutInitWindowSize(1000, 1000);
glutInitDisplayMode(GLUT_RGBA);
glutCreateWindow("Prog. 1.3");
glutDisplayFunc(display);
glutMainLoop();
return 0;
}
void display(void){
for (a = amin; a < amax; a += 0.001){
flag = 0;
for (i = 0; i < 100; i++){
y = x * x - a;
x = y;
if (fabs(y) > 10.0){ flag = 1; x = 0.0; break;}
}
for (i = 0; i < 50 && flag == 0; i++){
y = x * x - a;
x = y;
if (fabs(y) > 10.0){ x = 0.0; break; }
u = a * slx + u0;
v = y * sly + v0;
glColor3d(bgicolor[col].r, bgicolor[col].g, bgicolor[col].b);
glBegin(GL_POINTS);
glVertex2d(u, v);
glEnd();
}
}
glFlush();
}
終わりに
Processingの方が早いかと思いきや、意外と変わらないのでは。