4.4 Qt Graphics 场景中的二维空间变换

      本文是《用 Qt 实现电子白板》的其中一节,建议全章阅读。


        场景中的二维空间变换是针对控件的平移、缩放、旋转变换。

        在上一级我们介绍过场景中的交互逻辑,其中一大块就是操作空间变换,那些是空间变换的输入。另外,我们在《场景视图中二维空间变换矩阵的计算》中介绍了变换矩阵的计算方法。本文的重点在于对二维变换的管理。

二维空间变换API

        在 Qt Graphics 中,二维变换相关的 api 有(QGraphicsItem):

void setPos(const QPointF &pos);
void setRotation(qreal angle);
void setScale(qreal scale);
void setTransformOriginPoint(const QPointF &origin);
void setTransform(const QTransform &matrix, bool combine = false);
void setTransformations(const QList<QGraphicsTransform *> &transformations);

        按作用的先后顺序是:

  • Transform
  • Transformations
  • Rotation(TransformOriginPoint)
  • Scale(TransformOriginPoint)
  • Pos

        本身这些功能是有冗余的,比如 Transform 完全可以用 Transformations 增加一项代替。而 Rotation、Scale、Pos 则是为了方便使用,虽然实现与 Transformations 并不统一,但也是可以替代的。

        在电子白板中,没有使用 Rotation、Scale、Pos。这是为了降低二维变换的处理复杂度。而使用 Transform 仅仅是为了将控件的中心点变换到 {0,0} 位置。所有平移、缩放、旋转都是通过 Transformations 实现的。

        Transformations 表示的其实是一组有序的 QGraphicsTransform。在 QGraphicsTransform 内部使用 QMatrix4x4 这个 4x4 的矩阵表示变换,但是我们只使用了二维变换的能力,可以看成 3x3 的矩阵处理。

空间变换分解

        二维变换分解下来有平移、旋转、缩放三个部分,每部分都是一个 3x3 的矩阵,这里的任何二维变换都可以用这三个矩阵的乘积来表示。(参考 《场景视图中二维变换矩阵的计算》)

        A = S * R * T

        S(Scale)表示缩放矩阵

        R(Rotate)表示旋转矩阵

        T(Translate)表示平移矩阵

        3个有序变换(SRT)可以任意组合,但是要保持相对顺序,理论上共有8种组合。常见的组合有:

  • R * T变换物体只有位置变化,没有形状变化

        在有些情况下,需要能够抵消父节点(QGraphicsItem)的一部分变换。这就需要引入逆变换,即以上三个矩阵的逆矩阵 S^{-1}R^{-1}T^{-1}。因为变换离父节点更近,所以都应该安排在普通变换的后面,而且三种次序也是反过来的。

        综合起来,一个节点的变换有下列变换矩阵组成:A = S * R * T * T^{-1} * R^{-1} * S^{-1}

        与普通变换一样,逆变换也共有8种组合。常见的逆变换组合有:

  • S’:        主物体在变换时,子物体没有形变
  • T’R’ :    主物体在变换时,子物体没有位置变化
  • T’R’S’:  主物体在变换时,子物体相对主物体固定

         下面的代码在 ControlTransform 类(继承 QGraphicsTransform )中实现了 QGraphicsTransform 的 applyTo  方法,switch 分支对应不同的变换组合(共 16 个,普通变换、逆变换不会同时存在于一个 QGraphicsTransform  对象中)。

void ControlTransform::applyTo(QMatrix4x4 *matrix) const
{
    switch (type_) {
    case Identity:
        break;
    case Translate:
        *matrix = matrix->toTransform() * transform_->translate();
        break;
    case Rotate:
        *matrix = matrix->toTransform() * transform_->rotate();
        break;
    case RotateTranslate: // Frame
        *matrix = matrix->toTransform() * transform_->rotateTranslate();
        break;
    case Scale: // FrameItem
        *matrix = matrix->toTransform() * transform_->scale();
        break;
    case ScaleTranslate:
        *matrix = matrix->toTransform() * (transform_->scale() * transform_->translate());
        break;
    case ScaleRotate:
        *matrix = matrix->toTransform() * transform_->scaleRotate();
        break;
    case ScaleRotateTranslate: // PureItem
        *matrix = matrix->toTransform() * transform_->transform();
        break;
    case NoInvert:
         break;
    case InvertTranslate:
        *matrix = matrix->toTransform() * transform_->translate().inverted();
        break;
    case InvertRotate:
        *matrix = matrix->toTransform() * transform_->rotate().inverted();
        break;
    case InvertRotateTranslate:
        *matrix = matrix->toTransform() * transform_->rotateTranslate().inverted();
        break;
    case InvertScale:
        *matrix = matrix->toTransform() * transform_->scale().inverted();
        break;
    case InvertScaleTranslate:
        *matrix = matrix->toTransform() * (transform_->scale() * transform_->translate()).inverted();
        break;
    case InvertScaleRotate:
        *matrix = matrix->toTransform() * transform_->scaleRotate().inverted();
        break;
    case InvertScaleRotateTranslate:
        *matrix = matrix->toTransform() * transform_->transform().inverted();
        break;
    }
}

        这里的 ControlTransform 实现依赖空间变化计算类 ResourceTransform,多个 ControlTransform 可能共享同一个 ResourceTransform,一旦 ResourceTransform 有变化,ControlTransform 会通知节点触发更新。

ControlTransform::ControlTransform(ResourceTransform const & transform, ControlTransform::Type type)
    : transform_(&transform)
    , type_(type)
{
    QObject::connect(transform_, &ResourceTransform::changed,
                     this, &ControlTransform::update);
}

         一个节点 (QGraphicsItem)中可能有多个 ControlTransform 。

空间变换的使用

节点

相对节点

(父节点)

变换组合备注
画布场景缩放、平移
控件画布缩放、旋转、平移

        

控件外边框缩放
外边框画布旋转、平移

保持相对布局一致

替代控件的旋转、平移

选择框画布旋转、平移

边框不会缩放,保持各元素大小相对固定

同步控件的旋转、平移 

上下文菜单画布平移;逆缩放保持全局大小一致
上下文菜单场景平移
状态展示控件

平移;

(逆旋转),逆缩放

相对画布不旋转

相对画布大小固定

         该表描述了电子白板中的各个元素的空间变换方式。

         在相对于父节点的逆变换中,会共享父节点的 ResourceTransform,所以能够做到与父节点同步变化,起到对抗父节点空间变换的效果。

        另一个共享 ResourceTransform 的例子是在“选择框”中,为了与控件的旋转、平移保持一致,选择框动态绑定当前选中控件的 ResourceTransform。

        

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值