施泰纳球极平面射影
2022-1-11 ~ 2022-2-25
(0)
程序简介
Steiner's Porism,是同心圆套内切圆的动画。视频用了立体投影一词。我在视频中理解了原理,于是自己推公式才能还原出来。因为是三维,需要复习一下立体几何。
我说一下编程的故事吧,首先解决的是对称条件下小圆的半径,然后是投影至球上后,各个计算点与 y 轴的角度,最后是旋转后,比例的扩张,中心的平移距离,各个计算点的坐标。中途也出现了许多问题,一开始我用的是各个计算点与圆心的角度,其实是与 y 轴的角度;每个内切圆我代入了两个点,与大小圆的切点,这两个点反演的结果我一开始当成直径,事实上这两点是分别与大小圆圆心连线,相交的才是新圆心;坐标的矫正我一开始也是搞错了。不过这些问题都解决了,就挺好的。当然我还是有进步空间的,这个程序只能旋转 0 到 90 度,原视频可以等于 90 度,这时大小圆反演成平行线,还可以大于 90 度,这时大小圆角色互换,关于后面的情况,只能说有机会再试试能不能还原了。
第一次更新:
- 字符串更新,不再使用 char
- 参考资料加链接
第二次更新:
- 图片更新,减小边框
- 代码更新,现在兼容 vc2010,不兼容 vc6.0
vc2010:_stscanf_s,vc6.0:_stscanf
vc2010:_stprintf_s,vc6.0:_stprintf
程序执行效果
完整的程序源代码
////////////////////////////////////////
// 程序:施泰纳球极平面射影
// 作者:Gary
// 编译环境:Visual C++ 2010,EasyX_20211109
// 编写日期:2021-11-13
# include <math.h>
# include <graphics.h>
# include <string>
// 定义一个结构体,内切圆
struct Node
{
double x1, y1, z1, r1;
double x2, y2, z2, r2;
double posx, posy, r;
};
// 定义一个结构体,按钮
struct Node2
{
int posx1, posy1, posx2, posy2;
int mod;
double r;
LPTSTR text;
};
// 定义一个类
class Gary
{
public:
void carry (); // 主进程
void initialization (); // 初始化
void draw (); // 绘制,参数更新
void draw1 (); // 绘制
void move (); // 窗口主视角
private:
double a; // 对称情况下,北级点对小圆投影至球后,投影点和球心连线与水平面的夹角
double w; // 旋转角度
double pi; // 圆周率π
double R; // 大圆半径
double d; // 变换前后的比例尺
double r; // 对称情况下,小圆半径
double t; // 三维坐标直线参数
double r0; // 旋转前,投影点与 y 轴的距离
double b; // 旋转前,投影点与 y 轴的夹角
double d0; // 变换前后的坐标偏移量
double c; // 对称情况下,内切圆圆心的初始角度
int n; // 内切圆的数量
int exit1, exit2, exit3; // 进程控制
HWND hOut; // 画布
Node box[100]; // 内切圆
Node2 boxm[10]; // 按钮
};
// 绘制函数,参数更新函数
void Gary::draw ()
{
int i;
TCHAR s[20];
cleardevice ();
// 参数初始化
r = R * (1.0 - sin (pi / double (n))) / (1.0 + sin (pi / double (n)));
a = 90.0 - 2 * atan (r / R) * 180.0 / pi;
d = 2.0 / (tan ((90.0 + w) / 2.0 / 180.0 * pi) + tan ((90.0 - w) / 2.0 / 180.0 * pi));
d0 = R * ((tan ((90.0 + w) / 2.0 / 180.0 * pi) - tan ((90.0 - w) / 2.0 / 180.0 * pi)) / 2.0);
// 内切圆初始化
for (i = 0; i < n; i++)
{
box[i].r1 = r;
box[i].x1 = box[i].r1 * cos (2.0 * pi / double (n) * double (i) + c / 180.0 * pi);
box[i].y1 = box[i].r1 * sin (2.0 * pi / double (n) * double (i) + c / 180.0 * pi);
box[i].z1 = 0;
box[i].r2 = R;
box[i].x2 = box[i].r2 * cos (2.0 * pi / double (n) * double (i) + c / 180.0 * pi);
box[i].y2 = box[i].r2 * sin (2.0 * pi / double (n) * double (i) + c / 180.0 * pi);
box[i].z2 = 0;
}
// 小圆初始化
i = n;
box[i].r1 = r;
box[i].x1 = box[i].r1 * cos (0.0);
box[i].y1 = box[i].r1 * sin (0.0);
box[i].z1 = 0;
box[i].r2 = r;
box[i].x2 = box[i].r2 * cos (pi);
box[i].y2 = box[i].r2 * sin (pi);
box[i].z2 = 0;
// 大圆初始化
i = n + 1;
box[i].r1 = R;
box[i].x1 = box[i].r1 * cos (0.0);
box[i].y1 = box[i].r1 * sin (0.0);
box[i].z1 = 0;
box[i].r2 = R;
box[i].x2 = box[i].r2 * cos (pi);
box[i].y2 = box[i].r2 * sin (pi);
box[i].z2 = 0;
// 参数更新
for (i = n + 1; i >= 0; i--)
{
// 映射至球
t = 2.0 * R * R / (R * R + box[i].r1 * box[i].r1);
box[i].x1 *= t;
box[i].y1 *= t;
box[i].z1 = (1.0 - t) * R;
t = 2.0 * R * R / (R * R + box[i].r2 * box[i].r2);
box[i].x2 *= t;
box[i].y2 *= t;
box[i].z2 = (1.0 - t) * R;
// 旋转
r0 = sqrt (box[i].x1 * box[i].x1 + box[i].z1 * box[i].z1);
if (box[i].x1 == 0) { b = box[i].z1 > 0 ? 90 : -90; }
else { b = box[i].x1 > 0 ? (atan (box[i].z1 / box[i].x1) * 180.0 / pi) : (180.0 + atan (box[i].z1 / box[i].x1) * 180.0 / pi); }
box[i].x1 = r0 * cos ((w + b) * pi / 180.0);
box[i].y1 = box[i].y1;
box[i].z1 = r0 * sin ((w + b) * pi / 180.0);
r0 = sqrt (box[i].x2 * box[i].x2 + box[i].z2 * box[i].z2);
if (box[i].x2 == 0) { b = box[i].z2 > 0 ? 90 : -90; }
else { b = box[i].x2 > 0 ? (atan (box[i].z2 / box[i].x2) * 180.0 / pi) : (180.0 + atan (box[i].z2 / box[i].x2) * 180.0 / pi); }
box[i].x2 = r0 * cos ((w + b) * pi / 180.0);
box[i].y2 = box[i].y2;
box[i].z2 = r0 * sin ((w + b) * pi / 180.0);
// 映射至水平面
t = -R / (box[i].z1 - R);
box[i].x1 *= t;
box[i].y1 *= t;
box[i].z1 = 0;
t = -R / (box[i].z2 - R);
box[i].x2 *= t;
box[i].y2 *= t;
box[i].z2 = 0;
// 圆心矫正
if (box[i].y1 != 0 && box[i].y2 != 0 && w > 0)
{
box[i].posy = ((box[n].x1 + box[n].x2) / 2.0 - (box[n + 1].x1 + box[n + 1].x2) / 2.0) / ((box[i].x2 - (box[n + 1].x1 + box[n + 1].x2) / 2.0) / box[i].y2 - (box[i].x1 - (box[n].x1 + box[n].x2) / 2.0) / box[i].y1);
box[i].posx = (box[i].x2 - (box[n + 1].x1 + box[n + 1].x2) / 2.0) / box[i].y2 * box[i].posy + (box[n + 1].x1 + box[n + 1].x2) / 2.0;
}
else
{
box[i].posy = (box[i].y1 + box[i].y2) / 2.0;
box[i].posx = (box[i].x1 + box[i].x2) / 2.0;
}
box[i].r = sqrt ((box[i].posx - box[i].x2) * (box[i].posx - box[i].x2) + (box[i].posy - box[i].y2) * (box[i].posy - box[i].y2));
// 坐标,半径矫正
box[i].r *= d;
box[i].posx -= d0;
box[i].posx = 250 + box[i].posx * d;
box[i].posy = 250 + box[i].posy * d;
// 圆绘制
fillcircle (int (box[i].posx), int (box[i].posy), int (box[i].r));
}
// 按钮更新
boxm[5].posx1 = int (box[n].posx);
boxm[5].posy1 = int (box[n].posy);
boxm[6].posx1 = int (box[0].posx);
boxm[6].posy1 = int (box[0].posy);
// 按钮绘制
for (i = 0; i < 5; i++)
{
fillrectangle (boxm[i].posx1, boxm[i].posy1, boxm[i].posx2, boxm[i].posy2);
outtextxy (5 + boxm[i].posx1, 500 + 10 + 15, boxm[i].text);
}
// 参数绘制
_stprintf_s (s, _T ("%0.1d"), n); outtextxy (60, 500 + 10 + 15, s);
_stprintf_s (s, _T ("%0.0f"), w); outtextxy (170, 500 + 10 + 15, s);
_stprintf_s (s, _T ("%0.0f"), c); outtextxy (270, 500 + 10 + 15, s);
FlushBatchDraw ();
}
// 绘制函数
void Gary::draw1 ()
{
setfillcolor (LIGHTRED);
fillcircle (int (box[n].posx), int (box[n].posy), 5);
fillcircle (int (box[0].posx), int (box[0].posy), 5);
setfillcolor (WHITE);
FlushBatchDraw ();
}
// 初始化函数
void Gary::initialization ()
{
// 参数初始化
pi = acos (-1.0);
R = 200;
w = 0;
n = 4;
c = 0;
// 画布初始化
setbkcolor (WHITE);
setfillcolor (WHITE);
settextcolor (BLACK);
setlinecolor (BLACK);
// 按钮初始化
boxm[0].text = _T ("圆数:");
boxm[1].text = _T ("旋转角:");
boxm[2].text = _T ("初始角:");
boxm[3].text = _T ("退出");
boxm[4].text = _T ("重置");
for (int i = 0; i < 5; i++)
{
boxm[i].posx1 = 10 + 100 * i;
boxm[i].posy1 = 500;
boxm[i].posx2 = 100 + 100 * i;
boxm[i].posy2 = 560;
boxm[i].mod = 1;
}
boxm[5].mod = 2;
boxm[5].r = 5;
boxm[6].mod = 2;
boxm[6].r = 5;
draw ();
draw1 ();
FlushBatchDraw ();
}
// 窗口主视角函数,获取用户操作
void Gary::move ()
{
// 鼠标定义
ExMessage m;
TCHAR ss[20];
int i;
exit2 = 0;
while (exit2 == 0)
{
if (peekmessage (&m, EM_MOUSE | EM_KEY))
{
// 左键单击判断
if (m.message == WM_LBUTTONDOWN)
{
// 判断是否点击了按钮
for (i = 0; i < 7; i++)
{
if (boxm[i].mod == 1 && m.x > boxm[i].posx1 && m.y > boxm[i].posy1 && m.x < boxm[i].posx2 && m.y < boxm[i].posy2)
{
break;
}
else if (boxm[i].mod == 2 && (m.x - boxm[i].posx1) * (m.x - boxm[i].posx1) + (m.y - boxm[i].posy1) * (m.y - boxm[i].posy1) < boxm[i].r * boxm[i].r)
{
break;
}
}
switch (i)
{
// 设置圆数按钮
case 0:
{
InputBox (ss, 10, _T ("输入内切圆个数(3 ~ 99)"));
_stscanf_s (ss, _T ("%d"), &i);
if (i >= 3 && i <= 99)
{
n = int (i);
}
else
{
MessageBox (hOut, _T ("输入错误,不在范围内"), _T ("来自小豆子的提醒"), MB_OK);
}
draw ();
draw1 ();
break;
}
// 设置旋转角按钮
case 1:
{
InputBox (ss, 10, _T ("输入旋转角度(0 ~ 89)"));
_stscanf_s (ss, _T ("%d"), &i);
if (i >= 0 && i <= 89)
{
w = int (i);
}
else
{
MessageBox (hOut, _T ("输入错误,不在范围内"), _T ("来自小豆子的提醒"), MB_OK);
}
draw ();
draw1 ();
break;
}
// 设置初始角按钮
case 2:
{
InputBox (ss, 10, _T ("输入初始角度(0 ~ 360)"));
_stscanf_s (ss, _T ("%d"), &i);
if (i >= 0 && i <= 360)
{
c = int (i);
}
else
{
MessageBox (hOut, _T ("输入错误,不在范围内"), _T ("来自小豆子的提醒"), MB_OK);
}
draw ();
draw1 ();
break;
}
// 退出按钮
case 3:
{
exit2 = 1; exit1 = 1;
break;
}
// 重置按钮
case 4:
{
w = 0; n = 4; c = 0; draw (); draw1 ();
break;
}
// 旋转按钮
case 5:
{
exit3 = 0;
while (exit3 == 0)
{
if (peekmessage (&m, EM_MOUSE | EM_KEY))
{
// 鼠标移动来旋转
if (m.message == WM_MOUSEMOVE)
{
if (m.x < 250 && m.x > 50)
{
w = 89 * (250 - m.x) / 200;
draw ();
}
}
// 左键单击退出旋转
else if (m.message == WM_LBUTTONDOWN)
{
exit3 = 1;
}
}
}
draw1 ();
break;
}
// 平移按钮
case 6:
{
exit3 = 0;
while (exit3 == 0)
{
if (peekmessage (&m, EM_MOUSE | EM_KEY))
{
// 鼠标移动来平移
if (m.message == WM_MOUSEMOVE)
{
if (m.x < 500 && m.x > 0)
{
if (m.x == 250) { c = m.y > 250 ? 90 : -90; }
else { c = m.x > 250 ? (atan (double (m.y - 250) / double (m.x - 250)) * 180.0 / pi) : (180 + atan (double (m.y - 250) / double (m.x - 250)) * 180.0 / pi); }
draw ();
}
}
// 左键单击退出旋转
else if (m.message == WM_LBUTTONDOWN)
{
exit3 = 1;
}
}
}
draw1 ();
break;
}
default:break;
}
}
}
}
}
// 主进程
void Gary::carry ()
{
// 窗口定义
hOut = initgraph (501, 600);
SetWindowText (hOut, _T ("施泰纳球极平面射影"));
// 进程控制
exit1 = 0;
BeginBatchDraw ();
while (exit1 == 0)
{
initialization ();
move ();
}
EndBatchDraw ();
closegraph ();
}
// 主函数
int main (void)
{
Gary G;
G.carry ();
return 0;
}
添加评论
取消回复