Add ray picking to all graph types

Adding missing docs for picking

Task-number: QTBUG-129385
Change-Id: Ief9d3a0d50382ae937660f9566b1552c2444c9b6
Reviewed-by: Tomi Korpipää <tomi.korpipaa@qt.io>
This commit is contained in:
Sakaria Pouke 2024-11-26 17:09:18 +02:00
parent c89d24cdbf
commit 1530321fda
12 changed files with 363 additions and 0 deletions

View File

@ -1988,6 +1988,90 @@ bool QQuickGraphsBars::doPicking(QPointF position)
return true;
}
bool QQuickGraphsBars::doRayPicking(const QVector3D &origin, const QVector3D &direction)
{
if (!QQuickGraphsItem::doRayPicking(origin, direction))
return false;
m_selectionDirty = true;
QList<QQuick3DPickResult> pickResults = rayPickAll(origin, direction);
QQuick3DModel *selectedModel = nullptr;
QVector3D instancePos = {.0f, .0f, .0f};
if (!selectionMode().testFlag(QtGraphs3D::SelectionFlag::None)) {
if (!pickResults.isEmpty()) {
for (const auto &picked : std::as_const(pickResults)) {
if (const auto &hit = picked.objectHit()) {
if (hit == backgroundBB() || hit == background()) {
resetClickedStatus();
continue;
} else if (hit->objectName().contains(QStringLiteral("BarModel"))) {
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
selectedModel = hit;
for (const auto barlist : std::as_const(m_barModelsMap)) {
for (const auto barModel : *barlist) {
if (barModel->model == selectedModel) {
setSelectedBar(barModel->coord,
m_barModelsMap.key(barlist),
false);
}
}
}
break;
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
BarInstancing *barIns = static_cast<BarInstancing *>(hit->instancing());
// Prevents to select bars with a height of 0 which affect picking.
if (!barIns->dataArray().isEmpty()
&& barIns->dataArray().at(picked.instanceIndex())->heightValue
!= 0) {
selectedModel = hit;
instancePos = selectedModel->instancing()->instancePosition(
picked.instanceIndex());
for (const auto barlist : std::as_const(m_barModelsMap)) {
for (const auto barModel : *barlist) {
QList<BarItemHolder *> barItemList = barModel->instancing
->dataArray();
for (const auto bih : barItemList) {
if (bih->position == instancePos) {
setSelectedBar(bih->coord,
m_barModelsMap.key(barlist),
false);
if (isSliceEnabled())
setSliceActivatedChanged(true);
}
}
}
}
break;
}
}
} else if (hit->objectName().contains(QStringLiteral("ElementAxis"))) {
QPoint coord = invalidSelectionPosition();
if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Column)
&& selectedAxis() == axisX()) {
// Use row from previous selection in case of row + column mode
int previousRow = qMax(0, m_selectedBar.x());
coord = QPoint(previousRow, selectedLabelIndex());
} else if (selectionMode().testFlag(QtGraphs3D::SelectionFlag::Row)
&& selectedAxis() == axisZ()) {
// Use column from previous selection in case of row + column mode
int previousCol = qMax(0, m_selectedBar.y());
coord = QPoint(selectedLabelIndex(), previousCol);
}
for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++) {
if (it.key()->isVisible())
setSelectedBar(coord, it.key(), false);
}
break;
}
}
}
} else {
resetClickedStatus();
}
}
return true;
}
QAbstract3DAxis *QQuickGraphsBars::createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation)
{
QAbstract3DAxis *defaultAxis = 0;

View File

@ -168,6 +168,7 @@ protected:
void handleLabelCountChanged(QQuick3DRepeater *repeater, QColor axisLabelColor) override;
void updateSelectionMode(QtGraphs3D::SelectionFlags mode) override;
bool doPicking(QPointF position) override;
Q_REVISION(6, 9) bool doRayPicking(const QVector3D &origin, const QVector3D &direction) override;
QAbstract3DAxis *createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation) override;
void updateSliceItemLabel(const QString &label, QVector3D position) override;

View File

@ -397,6 +397,36 @@ constexpr float polarRoundness = 64.0f;
* \sa Custom3DItem::textureFile
*/
/*!
* \qmlmethod void GraphsItem3D::releaseCustomItem(Custom3DItem item)
*
* Gets ownership of \a item back and removes the \a item from the graph.
*
* \note If the same item is added back to the graph, the texture file needs to
* be re-set.
*
* \sa Custom3DItem::textureFile
*/
/*!
* \qmlmethod void GraphsItem3D::doPicking(QPoint point)
*
* Performs picking using view coordinates from \a point
* on the elements of the graph, selecting the first item hit.
* Default input handling performs this upon receiving the onTapped event.
*
* \sa selectedElement
*/
/*!
* \qmlmethod void GraphsItem3D::doPicking(QVector3D origin, QVector3D direction)
*
* Performs picking starting from \a origin and in \a direction
* on the elements of the graph, selecting the first item hit.
*
* \sa selectedElement
*/
/*!
* \qmlmethod int GraphsItem3D::selectedLabelIndex()
*
@ -5693,6 +5723,56 @@ bool QQuickGraphsItem::doPicking(QPointF point)
return true;
}
bool QQuickGraphsItem::doRayPicking(const QVector3D &origin, const QVector3D &direction)
{
checkSliceEnabled();
QList<QQuick3DPickResult> results = rayPickAll(origin, direction);
if (!m_customItemList.isEmpty()) {
// Try to pick custom item only
for (const auto &result : results) {
QCustom3DItem *customItem = m_customItemList.key(result.objectHit(), nullptr);
if (customItem) {
qsizetype selectedIndex = m_customItems.indexOf(customItem);
m_selectedCustomItemIndex = selectedIndex;
handleSelectedElementChange(QtGraphs3D::ElementType::CustomItem);
// Don't allow picking in subclasses if custom item is picked
return false;
}
}
}
for (const auto &result : results) {
if (!result.objectHit())
continue;
QString objName = result.objectHit()->objectName();
if (objName.contains(QStringLiteral("ElementAxisXLabel"))) {
for (int i = 0; i < repeaterX()->count(); i++) {
auto obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(i));
if (result.objectHit() == obj)
m_selectedLabelIndex = i;
}
handleSelectedElementChange(QtGraphs3D::ElementType::AxisXLabel);
break;
} else if (objName.contains(QStringLiteral("ElementAxisYLabel"))) {
handleSelectedElementChange(QtGraphs3D::ElementType::AxisYLabel);
break;
} else if (objName.contains(QStringLiteral("ElementAxisZLabel"))) {
for (int i = 0; i < repeaterX()->count(); i++) {
auto obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(i));
if (result.objectHit() == obj)
m_selectedLabelIndex = i;
}
handleSelectedElementChange(QtGraphs3D::ElementType::AxisZLabel);
break;
} else {
continue;
}
}
return true;
}
void QQuickGraphsItem::minimizeMainGraph()
{
QQuickItem *anchor = QQuickItemPrivate::get(this)->anchors()->fill();

View File

@ -519,6 +519,8 @@ public:
Q_INVOKABLE virtual bool doPicking(QPointF point);
Q_REVISION(6, 9) Q_INVOKABLE virtual bool doRayPicking(const QVector3D &origin, const QVector3D &direction);
void minimizeMainGraph();
int horizontalFlipFactor() const;

View File

@ -1405,6 +1405,36 @@ bool QQuickGraphsScatter::doPicking(QPointF position)
return true;
}
bool QQuickGraphsScatter::doRayPicking(const QVector3D &origin, const QVector3D &direction)
{
if (!QQuickGraphsItem::doRayPicking(origin, direction))
return false;
if (selectionMode() == QtGraphs3D::SelectionFlag::Item) {
QList<QQuick3DPickResult> results = rayPickAll(origin, direction);
if (!results.empty()) {
for (const auto &result : std::as_const(results)) {
if (const auto &hit = result.objectHit()) {
if (hit == backgroundBB() || hit == background()) {
clearSelectionModel();
continue;
}
if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
setSelected(hit);
break;
} else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
setSelected(hit, result.instanceIndex());
break;
}
}
}
} else {
clearSelectionModel();
}
}
return true;
}
void QQuickGraphsScatter::updateShadowQuality(QtGraphs3D::ShadowQuality quality)
{
// Were shadows visible before?

View File

@ -116,6 +116,7 @@ protected:
void calculateSceneScalingFactors() override;
void componentComplete() override;
bool doPicking(QPointF position) override;
Q_REVISION(6, 9) bool doRayPicking(const QVector3D &origin, const QVector3D &direction) override;
void updateShadowQuality(QtGraphs3D::ShadowQuality quality) override;
void updateLightStrength() override;
void startRecordingRemovesAndInserts() override;

View File

@ -2126,6 +2126,90 @@ bool QQuickGraphsSurface::doPicking(QPointF position)
return true;
}
bool QQuickGraphsSurface::doRayPicking(const QVector3D &origin, const QVector3D &direction)
{
if (!m_pickThisFrame && m_proxyDirty) {
m_pickThisFrame = true;
QVector3D toScene = mapFrom3DScene(origin);
m_lastPick = QPointF(toScene.x(), toScene.y());
for (auto model : m_model)
updateProxyModel(model);
return false;
}
if (!QQuickGraphsItem::doRayPicking(origin, direction))
return false;
m_selectionDirty = true;
QList<QQuick3DPickResult> pickResult = rayPickAll(origin, direction);
QVector3D pickedPos(0.0f, 0.0f, 0.0f);
QQuick3DModel *pickedModel = nullptr;
if (!selectionMode().testFlag(QtGraphs3D::SelectionFlag::None)) {
if (!sliceView() && selectionMode().testFlag(QtGraphs3D::SelectionFlag::Slice))
createSliceView();
if (!pickResult.isEmpty()) {
for (auto picked : pickResult) {
bool inBounds = qAbs(picked.position().y()) < scaleWithBackground().y();
if (inBounds && picked.objectHit()
&& picked.objectHit()->objectName().contains(QStringLiteral("ProxyModel"))) {
pickedPos = picked.position();
pickedModel = qobject_cast<QQuick3DModel *>(picked.objectHit()->parentItem());
bool visible = false;
for (auto model : m_model) {
if (model->model == pickedModel)
visible = model->series->isVisible();
}
if (!pickedPos.isNull() && visible)
break;
} else {
clearSelection();
for (auto model : m_model)
model->picked = false;
}
}
bool inRange = qAbs(pickedPos.x()) < scaleWithBackground().x()
&& qAbs(pickedPos.z()) < scaleWithBackground().z();
if (!pickedPos.isNull() && inRange) {
float min = -1.0f;
for (auto model : m_model) {
if (!model->series->isVisible()) {
model->picked = false;
continue;
}
model->picked = (model->model == pickedModel);
SurfaceVertex selectedVertex;
for (auto vertex : model->vertices) {
QVector3D pos = vertex.position;
float dist = pickedPos.distanceToPoint(pos);
if (selectedVertex.position.isNull() || dist < min) {
min = dist;
selectedVertex = vertex;
}
}
model->selectedVertex = selectedVertex;
if (!selectedVertex.position.isNull() && model->picked) {
model->series->setSelectedPoint(selectedVertex.coord);
setSlicingActive(false);
if (isSliceEnabled())
setSliceActivatedChanged(true);
}
}
}
} else {
clearSelection();
for (auto model : m_model)
model->picked = false;
}
}
return true;
}
void QQuickGraphsSurface::updateSelectedPoint()
{
bool labelVisible = false;

View File

@ -154,6 +154,7 @@ protected:
void updateLightStrength() override;
void handleThemeTypeChange() override;
bool doPicking(QPointF position) override;
Q_REVISION(6, 9) bool doRayPicking(const QVector3D &origin, const QVector3D &direction) override;
void createSliceView() override;
void updateSliceItemLabel(const QString &label, QVector3D position) override;

View File

@ -936,12 +936,31 @@ void Q3DGraphsWidgetItem::setMsaaSamples(int samples)
d->m_graphsItem->setMsaaSamples(samples);
}
/*!
* Performs picking using view coordinates from \a point
* on the elements of the graph, selecting the first item hit.
* Default input handling performs this upon receiving the onTapped event.
*
* \sa selectedElement
*/
void Q3DGraphsWidgetItem::doPicking(QPoint point)
{
Q_D(Q3DGraphsWidgetItem);
d->m_graphsItem->doPicking(point);
}
/*!
* Performs picking starting from \a origin and in \a direction
* on the elements of the graph, selecting the first item hit.
*
* \sa selectedElement
*/
void Q3DGraphsWidgetItem::doRayPicking(const QVector3D &origin, const QVector3D &direction)
{
Q_D(Q3DGraphsWidgetItem);
d->m_graphsItem->doRayPicking(origin, direction);
}
/*!
* \property Q3DGraphsWidgetItem::measureFps
*

View File

@ -225,6 +225,8 @@ public:
void setMsaaSamples(int samples);
void doPicking(QPoint point);
Q_REVISION(6, 9)
void doRayPicking(const QVector3D &origin, const QVector3D &direction);
float ambientLightStrength() const;
void setAmbientLightStrength(float newAmbientLightStrength);

View File

@ -106,6 +106,18 @@ Item {
to: Qt.vector3d(360, 0, 360)
}
}
MouseArea {
anchors.fill: parent
onClicked: {
var origin = view3d.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 0))
var far = view3d.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 1))
var direction = Qt.vector3d(far.x - origin.x,
far.y - origin.y,
far.z - origin.z );
bars.doRayPicking(origin, direction)
}
}
}
Slider {

View File

@ -92,6 +92,18 @@ Rectangle {
clearColor: scatter.theme.backgroundColor
backgroundMode: SceneEnvironment.Color
}
MouseArea {
anchors.fill: parent
onClicked: {
var origin = topLeftView.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 0))
var far = topLeftView.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 1))
var direction = Qt.vector3d(far.x - origin.x,
far.y - origin.y,
far.z - origin.z );
scatter.doRayPicking(origin, direction)
}
}
}
}
@ -114,6 +126,18 @@ Rectangle {
clearColor: scatter.theme.backgroundColor
backgroundMode: SceneEnvironment.Color
}
MouseArea {
anchors.fill: parent
onClicked: {
var origin = topRightView.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 0))
var far = topRightView.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 1))
var direction = Qt.vector3d(far.x - origin.x,
far.y - origin.y,
far.z - origin.z );
scatter.doRayPicking(origin, direction)
}
}
}
}
@ -137,6 +161,17 @@ Rectangle {
clearColor: scatter.theme.backgroundColor
backgroundMode: SceneEnvironment.Color
}
MouseArea {
anchors.fill: parent
onClicked: {
var origin = bottomLeftView.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 0))
var far = bottomLeftView.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 1))
var direction = Qt.vector3d(far.x - origin.x,
far.y - origin.y,
far.z - origin.z );
scatter.doRayPicking(origin, direction)
}
}
}
}
@ -160,6 +195,18 @@ Rectangle {
clearColor: scatter.theme.backgroundColor
backgroundMode: SceneEnvironment.Color
}
MouseArea {
anchors.fill: parent
onClicked: {
var origin = bottomRightView.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 0))
var far = bottomRightView.mapTo3DScene(Qt.vector3d(mouseX, mouseY, 1))
var direction = Qt.vector3d(far.x - origin.x,
far.y - origin.y,
far.z - origin.z );
scatter.doRayPicking(origin, direction)
}
}
}
}