You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何检测边缘透明的两个QGraphicsPixmapItem的非透明区域碰撞?

如何实现QGraphicsPixmapItem非透明区域的碰撞检测?

哈哈,这个问题我太熟了!Qt默认的collidingItems()确实只会盯着边界矩形做检测,完全不管透明像素的事。要实现非透明区域的精准碰撞,得自己给QGraphicsPixmapItem加些“定制化”的逻辑,这里给你三个实用的方案,按需选就行:

方案1:重写shape()方法(首推)

Qt的碰撞检测核心依据是shape()方法返回的QPainterPath,默认情况下这个路径就是item的边界矩形。我们可以把它替换成基于图片alpha通道的路径,这样collidingItems()就会自动用这个精准路径做碰撞判断了。

步骤很简单:

  1. 从QPixmap中提取alpha通道的遮罩(mask()方法)
  2. 把遮罩转换成QPainterPath
  3. 考虑item的变换(比如缩放、旋转),把路径做对应的变换后返回

代码示例:

#include <QGraphicsPixmapItem>
#include <QPainterPath>
#include <QRegion>

class CustomPixmapItem : public QGraphicsPixmapItem
{
public:
    using QGraphicsPixmapItem::QGraphicsPixmapItem;

    QPainterPath shape() const override
    {
        const QPixmap& pix = pixmap();
        if (pix.isNull())
            return QGraphicsPixmapItem::shape();

        // 获取alpha通道的遮罩
        QBitmap mask = pix.mask();
        // 将遮罩转换为QPainterPath
        QPainterPath path;
        path.addRegion(QRegion(mask));

        // 应用item的变换(比如缩放、旋转)
        return transform().map(path);
    }
};

注意:这个方案的优势是完全复用Qt内置的碰撞检测逻辑,性能比自己逐像素检测好很多,而且所有依赖shape()的方法(比如collidingItems()contains())都会自动生效。唯一需要注意的是,如果你的图片特别大,生成的QPainterPath会比较复杂,可能会有轻微的性能损耗,但大部分场景下完全够用。

方案2:自定义像素级碰撞检测函数

如果不想修改shape(),或者需要更灵活的判断逻辑(比如允许一定透明度的像素也算碰撞),可以自己写一个函数,先通过边界矩形过滤候选item,再在重叠区域做逐像素检测。

代码示例:

QList<QGraphicsItem*> CustomPixmapItem::transparentCollidingItems()
{
    QList<QGraphicsItem*> result;
    // 先获取边界矩形碰撞的候选,快速排除不可能的情况
    QList<QGraphicsItem*> candidates = collidingItems();

    for (QGraphicsItem* item : candidates)
    {
        CustomPixmapItem* pixItem = qgraphicsitem_cast<CustomPixmapItem*>(item);
        if (!pixItem) continue;

        // 计算两个item在全局坐标系下的重叠区域
        QRectF globalRect1 = mapToScene(boundingRect()).boundingRect();
        QRectF globalRect2 = pixItem->mapToScene(pixItem->boundingRect()).boundingRect();
        QRectF overlap = globalRect1.intersected(globalRect2);
        if (overlap.isEmpty()) continue;

        // 转换为各自本地坐标系的矩形
        QRectF localRect1 = mapFromScene(overlap).boundingRect().toAlignedRect();
        QRectF localRect2 = pixItem->mapFromScene(overlap).boundingRect().toAlignedRect();

        QImage img1 = pixmap().toImage();
        QImage img2 = pixItem->pixmap().toImage();

        bool hasCollision = false;
        // 遍历重叠区域的每个像素
        for (int x = localRect1.left(); x < localRect1.right(); ++x)
        {
            for (int y = localRect1.top(); y < localRect1.bottom(); ++y)
            {
                if (!img1.rect().contains(x, y)) continue;
                // 检查当前像素是否非透明
                if (img1.pixelColor(x, y).alpha() <= 0) continue;

                // 计算该像素在另一个item中的坐标
                QPointF scenePos = mapToScene(x, y);
                QPoint posInItem2 = pixItem->mapFromScene(scenePos).toPoint();
                if (!img2.rect().contains(posInItem2)) continue;

                // 检查另一个item的对应像素是否非透明
                if (img2.pixelColor(posInItem2).alpha() > 0)
                {
                    hasCollision = true;
                    goto endCheck; // 找到碰撞点就退出循环
                }
            }
        }
    endCheck:
        if (hasCollision)
            result.append(item);
    }
    return result;
}

这个方案的优点是灵活,可以自定义碰撞的判断规则(比如设置alpha阈值),缺点是逐像素检测的性能较差,适合item数量少、图片不大的场景。

方案3:重写collidesWithItem()方法

如果你希望调用默认的collidingItems()时自动使用像素级检测,可以重写collidesWithItem()方法,在里面实现自定义的碰撞逻辑。

代码示例:

bool CustomPixmapItem::collidesWithItem(const QGraphicsItem *other, Qt::ItemSelectionMode mode) const
{
    // 先调用默认的边界矩形检测,快速排除不可能的情况
    if (!QGraphicsPixmapItem::collidesWithItem(other, mode))
        return false;

    const CustomPixmapItem* pixItem = qgraphicsitem_cast<const CustomPixmapItem*>(other);
    if (!pixItem)
        // 如果不是自定义的pixmap item,就用默认逻辑
        return QGraphicsPixmapItem::collidesWithItem(other, mode);

    // 计算全局坐标系下的重叠区域
    QRectF globalOverlap = mapToScene(boundingRect()).intersected(pixItem->mapToScene(pixItem->boundingRect()));
    if (globalOverlap.isEmpty()) return false;

    QImage img1 = pixmap().toImage();
    QImage img2 = pixItem->pixmap().toImage();

    QPoint topLeft1 = mapFromScene(globalOverlap.topLeft()).toPoint();
    QPoint topLeft2 = pixItem->mapFromScene(globalOverlap.topLeft()).toPoint();

    // 遍历重叠区域的像素
    for (int x = 0; x < qRound(globalOverlap.width()); ++x)
    {
        for (int y = 0; y < qRound(globalOverlap.height()); ++y)
        {
            QPoint pos1(topLeft1.x() + x, topLeft1.y() + y);
            QPoint pos2(topLeft2.x() + x, topLeft2.y() + y);

            if (img1.rect().contains(pos1) && img2.rect().contains(pos2))
            {
                if (img1.pixelColor(pos1).alpha() > 0 && img2.pixelColor(pos2).alpha() > 0)
                {
                    return true;
                }
            }
        }
    }
    return false;
}

这个方案相当于把方案2的逻辑整合到Qt的碰撞检测流程里,调用collidingItems()时会自动使用这个自定义逻辑,不需要单独调用自己写的函数。


内容的提问来源于stack exchange,提问作者Skipper HUN

火山引擎 最新活动