匯東網


箭头围绕小球进行 3D 旋转

[編輯] [转简体]
|
作者:huidong | 分類:【編程】EasyX
[ 33 瀏覽 0 評論 1 贊 4 踩 ]

【概要】

【正文】

//////////////////////////////////////////////////
//
// 代码效果:箭头围绕小球进行 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;
}


[ 1] [ 4]


【評論區】 0 條評論

昵稱: 必填
聯系方式: 公開,填「無」亦可
驗證碼: 驗證碼