关于实现八象限中点画线算法的问题(Y轴倒置场景)
解决Y轴倒置场景下的八象限中点画线算法问题
首先要给你点个赞——已经搞定了4个象限的实现,这说明你对中点画线的核心逻辑已经摸透了,剩下的问题主要是主增量方向切换和Y轴倒置带来的符号调整,咱们一步步来拆解。
核心问题:剩余象限的本质是主方向从X切换到Y
你已经实现的Zone1、4、5、8都是|ΔX| ≥ |ΔY|的情况,主循环是按X递增/递减来走的;而剩下的Zone2、3、6、7都是|ΔY| > |ΔX|的情况,这时候主循环要切换为按Y递增/递减来走,决策参数和增量也要跟着调整(相当于把X和Y的角色互换)。
结合你Y轴倒置的场景(屏幕坐标系Y向下递增),我给你针对每个剩余象限的具体实现思路:
1. Zone2:X递增、Y递增、|ΔY|>|ΔX|(向右向下,Y变化更多)
void Line2D::MPOct2(const Window& window, const Point2D& start, const Point2D& end, const float& delta_x, const float& delta_y) const { Color color = mStartVtx.GetColor(); int int_delta_x = static_cast<int>(round(delta_x)); // 正 int int_delta_y = static_cast<int>(round(delta_y)); // 正 int start_x = static_cast<int>(round(start.GetX())); int start_y = static_cast<int>(round(start.GetY())); int const end_y = static_cast<int>(round(end.GetY())); int f = 2 * int_delta_x - int_delta_y; int f_se = 2 * (int_delta_x - int_delta_y); // 选SE(X+1,Y+1)的增量 int f_s = 2 * int_delta_x; // 选S(Y+1)的增量 while (start_y <= end_y) { window.SetPixel(start_x, start_y, color); if (f >= 0) { start_x++; start_y++; f += f_se; } else { start_y++; f += f_s; } } }
2. Zone3:X递减、Y递增、|ΔY|>|ΔX|(向左向下,Y变化更多)
void Line2D::MPOct3(const Window& window, const Point2D& start, const Point2D& end, const float& delta_x, const float& delta_y) const { Color color = mStartVtx.GetColor(); int int_delta_x = static_cast<int>(round(delta_x)); // 负 int int_delta_y = static_cast<int>(round(delta_y)); // 正 int start_x = static_cast<int>(round(start.GetX())); int start_y = static_cast<int>(round(start.GetY())); int const end_y = static_cast<int>(round(end.GetY())); int f = -2 * int_delta_x - int_delta_y; // -2*int_delta_x等价于2*|ΔX| int f_sw = 2 * (-int_delta_x - int_delta_y); // 选SW(X-1,Y+1)的增量 int f_s = -2 * int_delta_x; // 选S(Y+1)的增量 while (start_y <= end_y) { window.SetPixel(start_x, start_y, color); if (f >= 0) { start_x--; start_y++; f += f_sw; } else { start_y++; f += f_s; } } }
3. Zone6:X递减、Y递减、|ΔY|>|ΔX|(向左向上,Y变化更多)
void Line2D::MPOct6(const Window& window, const Point2D& start, const Point2D& end, const float& delta_x, const float& delta_y) const { Color color = mStartVtx.GetColor(); int int_delta_x = static_cast<int>(round(delta_x)); // 负 int int_delta_y = static_cast<int>(round(delta_y)); // 负 int start_x = static_cast<int>(round(start.GetX())); int start_y = static_cast<int>(round(start.GetY())); int const end_y = static_cast<int>(round(end.GetY())); int f = -2 * int_delta_x + int_delta_y; // +int_delta_y等价于-2*|ΔY| int f_nw = 2 * (-int_delta_x + int_delta_y); // 选NW(X-1,Y-1)的增量 int f_n = -2 * int_delta_x; // 选N(Y-1)的增量 while (start_y >= end_y) { window.SetPixel(start_x, start_y, color); if (f >= 0) { start_x--; start_y--; f += f_nw; } else { start_y--; f += f_n; } } }
4. Zone7:X递增、Y递减、|ΔY|>|ΔX|(向右向上,Y变化更多)
void Line2D::MPOct7(const Window& window, const Point2D& start, const Point2D& end, const float& delta_x, const float& delta_y) const { Color color = mStartVtx.GetColor(); int int_delta_x = static_cast<int>(round(delta_x)); // 正 int int_delta_y = static_cast<int>(round(delta_y)); // 负 int start_x = static_cast<int>(round(start.GetX())); int start_y = static_cast<int>(round(start.GetY())); int const end_y = static_cast<int>(round(end.GetY())); int f = 2 * int_delta_x + int_delta_y; // +int_delta_y等价于-2*|ΔY| int f_ne = 2 * (int_delta_x + int_delta_y); // 选NE(X+1,Y-1)的增量 int f_n = 2 * int_delta_x; // 选N(Y-1)的增量 while (start_y >= end_y) { window.SetPixel(start_x, start_y, color); if (f >= 0) { start_x++; start_y--; f += f_ne; } else { start_y--; f += f_n; } } }
更优方案:用坐标变换统一处理所有象限
写8份代码确实容易出错,更高效的方式是把任意象限的线段转换为Zone1的情况,计算后再映射回原坐标,这样只需要维护一份Zone1的代码:
void Line2D::MPLine(const Window& window, const Point2D& start, const Point2D& end) const { Color color = mStartVtx.GetColor(); float dx_float = end.GetX() - start.GetX(); float dy_float = end.GetY() - start.GetY(); int dx = static_cast<int>(round(dx_float)); int dy = static_cast<int>(round(dy_float)); int x0 = static_cast<int>(round(start.GetX())); int y0 = static_cast<int>(round(start.GetY())); // 确定变换参数 int sx = dx >= 0 ? 1 : -1; int sy = dy >= 0 ? 1 : -1; int abs_dx = abs(dx); int abs_dy = abs(dy); bool swap = false; // 如果Y变化更大,交换X和Y if (abs_dy > abs_dx) { swap = true; std::swap(abs_dx, abs_dy); } // 用Zone1的逻辑计算变换后的点 int f = 2 * abs_dy - abs_dx; int f_e = 2 * abs_dy; int f_ne = 2 * (abs_dy - abs_dx); int x = 0, y = 0; while (x <= abs_dx) { // 映射回原始坐标 int draw_x = x0 + (swap ? sx * y : sx * x); int draw_y = y0 + (swap ? sy * x : sy * y); window.SetPixel(draw_x, draw_y, color); if (f >= 0) { y++; f += f_ne; } else { f += f_e; } x++; } }
这个方案的核心是通过符号翻转和坐标交换,把所有象限的线段都转换成Zone1(X递增、Y递增、|X|≥|Y|)的标准情况,用你已经验证过的Zone1逻辑计算点,最后再映射回原始屏幕坐标,完美解决Y轴倒置的问题,还能大幅减少代码量。
内容的提问来源于stack exchange,提问作者user3770234




