箭头围绕小球进行 3D 旋转
[編輯] [转简体] (简体译文)
|
作者:huidong
| 分類:【編程】EasyX
[
42 瀏覽
0 評論
3 贊
6 踩
]
概要
正文
////////////////////////////////////////////////// // // 代码效果:箭头围绕小球进行 3D 旋转 // 作 者:huidong <mailhuid@163.com> // 编译环境:Visual Studio 2022 + EasyX_20220901 // 完成时间:2024.6.11 // #include <easyx.h> #include <math.h> #define PI 3.1415926 // 主球和轨迹圆的半径 int nR_ball = 100; int nR_arrow = 130; // 箭头长度(包括直线部分和三角形部分) int nArrowLength = 120; int nArrowHeadLength = 50; // 特指箭头的三角形部分 int nArrowHeigth = 40; int nArrowHeadHeigth = 60; // 绘图设备的渲染原点(仅用于调整绘制位置) int nX0 = 320; int nY0 = 240; // 视野尽头 y 坐标 int nViewEndY = -1000; // 判断一个弧度值是否位于某开区间内(同时自动匹配该区间的相邻周期区间) bool IsInRange(double _X, double _Begin, double _End) { return (_X > _Begin && _X < _End) || (_X > _Begin - 2 * PI && _X < _End - 2 * PI) || (_X > _Begin + 2 * PI && _X < _End + 2 * PI); } // 将一个弧度转换到 [0, 2π) 内 double GetStandardRadian(double _X) { double X = _X; while (!(X >= 0 && X < 2 * PI)) { if (X < 0) X += 2 * PI; else if (X >= 2 * PI) X -= 2 * PI; } return X; } // 获取箭头图案在某个点的厚度 // p_dRelativeRadian 该点相对于 A 点的弧度 int GetPatternThickness_Point(double p_dRelativeRadian, bool p_bIsLeftHanded) { // 整个箭头投影到其轨迹圆上的圆心角弧度 double dTotalRadian = (double)nArrowLength / nR_arrow; // 箭头的三角形部分对应圆心角弧度 double dHeadRadian = (double)nArrowHeadLength / nR_arrow; double dRelativeRadian = p_dRelativeRadian; // 若箭头朝右,则变换相对弧度 if (!p_bIsLeftHanded) { dRelativeRadian = dTotalRadian - dRelativeRadian; } // 根据相对弧度给出该点的绘制厚度 // 箭身部分 if (IsInRange(dRelativeRadian, 0, dTotalRadian - dHeadRadian)) { return nArrowHeigth; } // 箭头部分 else if (IsInRange(dRelativeRadian, dTotalRadian - dHeadRadian, dTotalRadian)) { return (int)(GetStandardRadian(dTotalRadian - dRelativeRadian) * ((double)nArrowHeadHeigth / dHeadRadian)); } // 异常,所给点不在箭头范围内 else { return 0; } } // 绘制箭头上某个点的图像 // p_nX 该点 x 坐标 // 需要此参数是因为在绘制时如果使用 p_dCurrentRadian 计算绘制使用的 x 坐标 // 则需要在 sin 值上乘以轨迹圆半径,可能导致所得 x 坐标过于离散而不连续从而产生绘制缺口 void DrawArrow_Point(double p_dARadian, double p_dBRadian, double p_dCurrentRadian, int p_nX, bool p_bIsLeftHanded) { // 该点相对于 A 点的弧度 double dRelativeRadian = p_dCurrentRadian - p_dARadian; // 该点的 y 坐标 double dY = nR_arrow * sin(p_dCurrentRadian); // 根据 y 坐标计算该点绘制时的绘制大小比例和亮度比例 // 某一点的 y 坐标为 0 时,绘制大小比例为 1,在视野尽头时为 0 // 某一点位于轨迹圆的正背面时,亮度比例为 0,在正前面时为 1 double dThicknessRatio = (double)(dY - nViewEndY) / abs(nViewEndY); double dLightnessRatio = (double)(dY + nR_arrow) / (2 * nR_arrow); // 获取该点在所定义图案上的理论绘制厚度 int nThickness = GetPatternThickness_Point(dRelativeRadian, p_bIsLeftHanded); if (dThicknessRatio >= 2) { Sleep(1); } // 根据绘制大小比例计算实际的绘制厚度 int nThickness_real = (int)(nThickness * dThicknessRatio); // 根据亮度比例计算实际的绘制亮度 COLORREF color = GREEN; COLORREF color_real; float h, s, l, l_real; RGBtoHSL(color, &h, &s, &l); l_real = (float)(l * dLightnessRatio); color_real = HSLtoRGB(h, s, l_real); // 绘制 setlinecolor(color_real); line(nX0 + p_nX, nY0 - nThickness_real / 2, nX0 + p_nX, nY0 + nThickness_real / 2); } // 渲染箭头围绕圆的画面 // p_dRadian 箭头中心弧度数 // p_bIsLeftHanded 绘制的箭头方向是否左旋 void Render(double p_dRadian, bool p_bIsLeftHanded) { // 半箭头长对应的弧度 double dHalfLengthRadian = ((double)nArrowLength / 2) / nR_arrow; // 计算箭头端点弧度,A 端对应小弧度,B 端对应大弧度 double dARadian = p_dRadian - dHalfLengthRadian; double dBRadian = p_dRadian + dHalfLengthRadian; // 可视区域对应的弧度(主球对箭头轨迹的遮挡产生了视野盲区) double dVisibleSpotRadian_0 = acos((double)nR_ball / nR_arrow); double dVisibleSpotRadian_Begin = -dVisibleSpotRadian_0; double dVisibleSpotRadian_End = PI + dVisibleSpotRadian_0; // 绘制主圆 setfillcolor(RGB(211, 211, 211)); solidcircle(nX0, nY0, nR_ball); // 对箭头轨迹圆沿 x 轴进行扫描 for (int x = -nR_arrow; x <= nR_arrow; x++) { double dIntersectionRadian[2]; // 扫描线与箭头轨迹圆交点对应的弧度 // 两个交点对应的弧度,第一个在 [0, π] 内,第二个在 [π, 2π) 内 dIntersectionRadian[0] = acos((double)x / nR_arrow); dIntersectionRadian[1] = GetStandardRadian(-dIntersectionRadian[0] + 2 * PI); // 记录两个弧度是否位于 AB 弧度内且不在视野盲区 bool bValid[2]; for (int i = 0; i < 2; i++) { bValid[i] = IsInRange(dIntersectionRadian[i], dARadian, dBRadian) && IsInRange(dIntersectionRadian[i], dVisibleSpotRadian_Begin, dVisibleSpotRadian_End); } // 判断绘制顺序 // 数组中的值按顺序用 0, 1 代表第几步绘制哪个点,-1 表示不绘制 int pPaintOrder[2] = { -1, -1 }; // 两个点都需要绘制 if (bValid[0] && bValid[1]) { // 确保先绘制更远的点 if (sin(dIntersectionRadian[0]) < sin(dIntersectionRadian[1])) { pPaintOrder[0] = 0; pPaintOrder[1] = 1; } else { pPaintOrder[0] = 1; pPaintOrder[1] = 0; } } else if (bValid[0]) { pPaintOrder[0] = 0; } else if (bValid[1]) { pPaintOrder[0] = 1; } // 按顺序绘制各个点 for (int i = 0; i < 2; i++) { int nIndex = pPaintOrder[i]; if (nIndex != -1) { DrawArrow_Point(dARadian, dBRadian, dIntersectionRadian[nIndex], x, p_bIsLeftHanded); } } } } int main() { initgraph(640, 480); setbkcolor(LIGHTBLUE); BeginBatchDraw(); // 使箭头的弧度逐帧变化,然后绘制 for (int i = 0;; i++) { double radian = PI * (double)i / 16.0; if (radian >= 2 * PI) i = 0; cleardevice(); Render(radian, true); FlushBatchDraw(); Sleep(50); } EndBatchDraw(); getmessage(EX_KEY); return 0; }