定义
- o: 圆心
- p1: 圆弧起点
- p2: 圆弧终点
- a: 弧度(逆时针方向)
- a1: 以圆心为原点建轴,圆弧起点到正半轴的弧度
- a2: 以圆心为原点建轴,圆弧终点到正半轴的弧度
推导
p 1 − o = r { c o s ( a 1 ) , s i n ( a 1 ) } p 2 − o = r { c o s ( a 2 ) , s i n ( a 2 ) } a 2 − a 1 = a p 2 − p 1 = r { c o s ( a 2 ) − c o s ( a 1 ) , s i n ( a 2 ) − s i n ( a 1 ) } d x = r ( c o s ( a 2 ) − c o s ( a 1 ) ) d y = r ( s i n ( a 2 ) − s i n ( a 1 ) ) d x d y = c o s ( a + a 1 ) − c o s ( a 1 ) s i n ( a + a 1 ) − s i n ( a 1 ) d x d y = c o s ( a ) c o s ( a 1 ) − s i n ( a ) s i n ( a 1 ) − c o s ( a 1 ) s i n ( a ) c o s ( a 1 ) + c o s ( a ) s i n ( a 1 ) − s i n ( a 1 ) d x d y = ( c o s ( a ) − 1 ) c o s ( a 1 ) − s i n ( a ) s i n ( a 1 ) ( c o s ( a ) − 1 ) s i n ( a 1 ) + s i n ( a ) c o s ( a 1 ) ( ( c o s ( a ) − 1 ) d x + s i n ( a ) d y ) s i n ( a 1 ) = ( ( c o s ( a ) − 1 ) d y − s i n ( a ) d x ) c o s ( a 1 ) t a n ( a 1 ) = s i n ( a 1 ) c o s ( a 1 ) = ( c o s ( a ) − 1 ) d y − s i n ( a ) d x ( c o s ( a ) − 1 ) d x + s i n ( a ) d y \begin{align} p_1-o=r\{cos(a_1),sin(a_1)\} \\ p_2-o=r\{cos(a_2),sin(a_2)\} \\ a_2-a_1=a \\ p_2-p_1=r\{cos(a_2)-cos(a_1),sin(a_2)-sin(a_1)\} \\ dx=r(cos(a_2)-cos(a_1)) \\ dy=r(sin(a_2)-sin(a_1)) \\ \dfrac{dx}{dy}=\dfrac{cos(a+a_1)-cos(a_1)}{sin(a+a_1)-sin(a_1)} \\ \dfrac{dx}{dy}=\dfrac{cos(a)cos(a_1)-sin(a)sin(a_1)-cos(a_1)}{sin(a)cos(a_1)+cos(a)sin(a_1)-sin(a_1)} \\ \dfrac{dx}{dy}=\dfrac{(cos(a)-1)cos(a_1)-sin(a)sin(a_1)}{(cos(a)-1)sin(a_1)+sin(a)cos(a_1)} \\ ((cos(a)-1)dx+sin(a)dy)sin(a_1)=((cos(a)-1)dy-sin(a)dx)cos(a_1) \\ tan(a_1)=\dfrac{sin(a_1)}{cos(a_1)}=\dfrac{(cos(a)-1)dy-sin(a)dx}{(cos(a)-1)dx+sin(a)dy} \end{align} p1−o=r{cos(a1),sin(a1)}p2−o=r{cos(a2),sin(a2)}a2−a1=ap2−p1=r{cos(a2)−cos(a1),sin(a2)−sin(a1)}dx=r(cos(a2)−cos(a1))dy=r(sin(a2)−sin(a1))dydx=sin(a+a1)−sin(a1)cos(a+a1)−cos(a1)dydx=sin(a)cos(a1)+cos(a)sin(a1)−sin(a1)cos(a)cos(a1)−sin(a)sin(a1)−cos(a1)dydx=(cos(a)−1)sin(a1)+sin(a)cos(a1)(cos(a)−1)cos(a1)−sin(a)sin(a1)((cos(a)−1)dx+sin(a)dy)sin(a1)=((cos(a)−1)dy−sin(a)dx)cos(a1)tan(a1)=cos(a1)sin(a1)=(cos(a)−1)dx+sin(a)dy(cos(a)−1)dy−sin(a)dx
代码
因为 Qt 窗口坐标系与笛卡尔坐标系在视觉上是垂直翻转的,故作顺逆时针翻转变换
#include <QtCore>
#include <QPainter>
void drawArc(QPainter* painter, const QPointF& p1, const QPointF& p2, double radian) {
//! 方向翻转
const auto& q1 = p2;
const auto& q2 = p1;
//! 参数计算
const auto dx = q2.x() - q1.x();
const auto dy = q2.y() - q1.y();
const auto u = cos(radian) - 1;
const auto v = sin(radian);
const auto a1 = atan2(u * dy - v * dx, u * dx + v * dy);
const auto a2 = a1 + radian;
const auto r = fabs(dx) < 1e-6 ? dy / (sin(a2) - sin(a1)) : dx / (cos(a2) - cos(a1));
const auto ox = q1.x() - r * cos(a1);
const auto oy = q1.y() - r * sin(a1);
const auto bb = QRectF(ox - r, oy - r, r * 2, r * 2);
//! 方向翻转
const auto start = -qRadiansToDegrees(a1) * 16;
const auto span = -qRadiansToDegrees(radian) * 16;
//! 圆弧绘制
painter->drawArc(bb, start, span);
}
写在后头
上述代码中之所以翻转是为了在视觉上得到与常规笛卡尔坐标系相同的结果,实际经过顺逆时钟变换过后,代码中计算得出的圆心、弧度等值并不与形参对应。
若要获取实际的数据,只需将代码中方向翻转的部分去除即可。
而为了实现视觉上的一致性,之所以要进行顺逆时钟翻转的原因主要有两点:
- 调用绘制时,通常会默认传入参数是遵从 Qt 窗口坐标系的,但又会潜在地期望绘制结果是遵从笛卡尔坐标系的,于是坐标系之间就产生了分歧,需要处理垂直翻转的问题。
- QPainter 提供的 drawArc 方法的逆时针是习惯下的,视觉上的逆时针,而非 Qt 窗口坐标系下的逆时针(其实就是第 1 点)。