Kuesa Drill-Experience QML Example
#include "screencontroller.h"
#include <Kuesa/View>
#include <Kuesa/TransformTracker>
#include <Kuesa/SteppedAnimationPlayer>
#include <KuesaUtils/viewconfiguration.h>
#include <Qt3DRender/QObjectPicker>
#include <Qt3DRender/QPickEvent>
#include <array>
namespace {
using Bit = ScreenController::Bit;
constexpr std::array<Bit, 20> bits = {
Bit::Drill1,
Bit::Drill2,
Bit::Drill3,
Bit::Drill4,
Bit::Drill5,
Bit::Drill6,
Bit::ScrewHex,
Bit::ScrewHexMedium,
Bit::ScrewHexSmall,
Bit::ScrewHexTiny,
Bit::ScrewTorx,
Bit::ScrewTorxMedium,
Bit::ScrewTorxSmall,
Bit::ScrewTorxTiny,
Bit::ScrewPhilips,
Bit::ScrewPhilipsMedium,
Bit::ScrewPhilipsSmall,
Bit::ScrewFlat,
Bit::ScrewFlatMedium,
Bit::ScrewFlatSmall,
};
constexpr std::array<Bit, 14> screwDriverBits = {
Bit::ScrewHex,
Bit::ScrewHexMedium,
Bit::ScrewHexSmall,
Bit::ScrewHexTiny,
Bit::ScrewTorx,
Bit::ScrewTorxMedium,
Bit::ScrewTorxSmall,
Bit::ScrewTorxTiny,
Bit::ScrewPhilips,
Bit::ScrewPhilipsMedium,
Bit::ScrewPhilipsSmall,
Bit::ScrewFlat,
Bit::ScrewFlatMedium,
Bit::ScrewFlatSmall,
};
constexpr std::array<Bit, 6> drillingBits = {
Bit::Drill1,
Bit::Drill2,
Bit::Drill3,
Bit::Drill4,
Bit::Drill5,
Bit::Drill6,
};
constexpr std::array<Bit, 2> metalDrillingBits = {
Bit::Drill1,
Bit::Drill2,
};
constexpr std::array<Bit, 2> concreteDrillingBits = {
Bit::Drill5,
Bit::Drill6,
};
constexpr std::array<Bit, 2> woodDrillingBits = {
Bit::Drill3,
Bit::Drill4,
};
QString gltfBitName(Bit bit)
{
switch (bit) {
case Bit::None:
Q_UNREACHABLE();
case Bit::Drill1:
return QStringLiteral("Tool_Drill1");
case Bit::Drill2:
return QStringLiteral("Tool_Drill2");
case Bit::Drill3:
return QStringLiteral("Tool_Drill3");
case Bit::Drill4:
return QStringLiteral("Tool_Drill4");
case Bit::Drill5:
return QStringLiteral("Tool_Drill5");
case Bit::Drill6:
return QStringLiteral("Tool_Drill6");
case Bit::ScrewHex:
return QStringLiteral("Tool_Hex");
case Bit::ScrewHexMedium:
return QStringLiteral("Tool_HexMed");
case Bit::ScrewHexSmall:
return QStringLiteral("Tool_HexSmall");
case Bit::ScrewHexTiny:
return QStringLiteral("Tool_HexTiny");
case Bit::ScrewTorx:
return QStringLiteral("Tool_Torx");
case Bit::ScrewTorxMedium:
return QStringLiteral("Tool_TorxMed");
case Bit::ScrewTorxSmall:
return QStringLiteral("Tool_TorxSmall");
case Bit::ScrewTorxTiny:
return QStringLiteral("Tool_TorxTiny");
case Bit::ScrewPhilips:
return QStringLiteral("Tool_Philips");
case Bit::ScrewPhilipsMedium:
return QStringLiteral("Tool_PhilipsMed");
case Bit::ScrewPhilipsSmall:
return QStringLiteral("Tool_PhilipsSmall");
case Bit::ScrewFlat:
return QStringLiteral("Tool_Flat");
case Bit::ScrewFlatMedium:
return QStringLiteral("Tool_FlatMed");
case Bit::ScrewFlatSmall:
return QStringLiteral("Tool_FlatSmall");
}
Q_UNREACHABLE();
}
void ensureClockOnAnimationPlayer(Kuesa::AnimationPlayer *player)
{
Qt3DAnimation::QClock *c = player->clock();
const bool hasClock = c != nullptr;
if (!hasClock) {
c = new Qt3DAnimation::QClock;
player->setClock(c);
}
}
}
ScreenController::ScreenController(Qt3DCore::QNode *parent)
: AbstractScreenController(parent)
{
QObject::connect(this, &ScreenController::selectedPartChanged,
this, &ScreenController::updateSceneConfiguration);
KuesaUtils::SceneConfiguration *configuration = new KuesaUtils::SceneConfiguration();
configuration->setSource(QUrl(QStringLiteral("qrc:/drill/drill.gltf")));
setSceneConfiguration(configuration);
m_mainViewConfiguration = new KuesaUtils::ViewConfiguration;
m_mainViewConfiguration->setClearColor(QColor(Qt::transparent));
m_mainViewConfiguration->setCameraName(QStringLiteral("CamOrbitCenter.CamOrbit"));
m_mainViewConfiguration->setLayerNames({ QStringLiteral("LayerDevice"), QStringLiteral("LayerEnv") });
m_mainViewConfiguration->setSkinning(true);
configuration->addViewConfiguration(m_mainViewConfiguration);
m_detailViewConfiguration = new KuesaUtils::ViewConfiguration;
m_detailViewConfiguration->setViewportRect({ 0.7f, 0.0f, 0.0f, 0.0f });
m_detailViewConfiguration->setClearColor(QColor(Qt::gray));
m_detailViewConfiguration->setLayerNames({ QStringLiteral("LayerDevice") });
configuration->addViewConfiguration(m_detailViewConfiguration);
{
const std::pair<QString, SelectablePart> partLabelNodes[] = {
{ QStringLiteral("Drill.LABEL_Trigger"), SelectablePart::Trigger },
{ QStringLiteral("Drill.LABEL_Chuck"), SelectablePart::Chuck },
{ QStringLiteral("Drill.LABEL_DirSwitch"), SelectablePart::DirectionSwitch },
{ QStringLiteral("Drill.LABEL_Battery"), SelectablePart::BatteryPack },
{ QStringLiteral("Drill.LABEL_Clutch"), SelectablePart::NoPartSelected },
{ QStringLiteral("Drill.LABEL_Kdab"), SelectablePart::NoPartSelected },
{ QStringLiteral("Drill.LABEL_Motor"), SelectablePart::NoPartSelected },
{ QStringLiteral("Drill.LABEL_Bits"), SelectablePart::NoPartSelected },
};
for (const auto &nodeNamePartPair : partLabelNodes) {
auto *tracker = new Kuesa::TransformTracker();
tracker->setName(nodeNamePartPair.first);
m_mainViewConfiguration->addTransformTracker(tracker);
auto *partLabel = new PartLabel(nodeNamePartPair.first,
nodeNamePartPair.second,
tracker,
this);
m_partLabels.push_back(partLabel);
}
}
{
m_cameraAnimationPlayer = new Kuesa::AnimationPlayer;
m_cameraAnimationPlayer->setClip(QStringLiteral("AnimCamOrbit"));
m_cameraAnimationPlayer->setLoopCount(Kuesa::AnimationPlayer::Infinite);
m_cameraAnimationPlayer->setRunning(true);
configuration->addAnimationPlayer(m_cameraAnimationPlayer);
m_batteryInOutAnimationPlayer = new Kuesa::AnimationPlayer;
m_batteryInOutAnimationPlayer->setClip(QStringLiteral("AnimBatteryOut"));
configuration->addAnimationPlayer(m_batteryInOutAnimationPlayer);
m_directionSwitchAnimationPlayer = new Kuesa::AnimationPlayer;
m_directionSwitchAnimationPlayer->setClip(QStringLiteral("AnimChangeDirectionLR"));
configuration->addAnimationPlayer(m_directionSwitchAnimationPlayer);
m_triggerAnimationPlayer = new Kuesa::AnimationPlayer;
m_triggerAnimationPlayer->setClip(QStringLiteral("AnimTriggerPress"));
configuration->addAnimationPlayer(m_triggerAnimationPlayer);
m_drillAnimationPlayer = new Kuesa::AnimationPlayer;
m_drillAnimationPlayer->setClip(QStringLiteral("AnimDrillCW"));
configuration->addAnimationPlayer(m_drillAnimationPlayer);
m_toolInOutAnimationPlayer = new Kuesa::AnimationPlayer;
m_toolInOutAnimationPlayer->setClip(QStringLiteral("AnimToolIn"));
configuration->addAnimationPlayer(m_toolInOutAnimationPlayer);
m_steppedPlayer = new Kuesa::SteppedAnimationPlayer();
m_steppedPlayer->setClip(QStringLiteral("AnimGuideSteps"));
m_steppedPlayer->setAnimationNames({ QStringLiteral("AnimGuideAnim") });
m_animationClock = new Qt3DAnimation::QClock;
m_steppedPlayer->setClock(m_animationClock);
configuration->addAnimationPlayer(m_steppedPlayer);
}
{
m_insertedDrillBitTranform = new Qt3DCore::QTransform();
m_insertedDrillBitTranform->setParent(sceneConfiguration()->sceneEntity());
m_insertedDrillBitTranform->setRotationX(90);
}
QObject::connect(configuration, &KuesaUtils::SceneConfiguration::loadingDone,
this, [this] {
addObjectPickersOnBit();
setPartLabelNames();
});
QObject::connect(this, &ScreenController::bitChanged,
this, &ScreenController::loadDrillBit);
QObject::connect(m_cameraAnimationPlayer, &Kuesa::AnimationPlayer::normalizedTimeChanged,
this, &ScreenController::positionOnCameraOrbitChanged);
QObject::connect(configuration, &KuesaUtils::SceneConfiguration::unloadingDone, this, [this] {
setSelectedPart(SelectablePart::NoPartSelected);
m_originalDrillBitParent = nullptr;
});
updateSceneConfiguration();
}
ScreenController::~ScreenController()
{
if (m_mainViewConfiguration && !m_mainViewConfiguration->parent())
delete m_mainViewConfiguration;
if (m_detailViewConfiguration && !m_detailViewConfiguration->parent())
delete m_detailViewConfiguration;
}
void ScreenController::playAnimationBackAndForth(Kuesa::AnimationPlayer *player, int delay)
{
ensureClockOnAnimationPlayer(player);
auto connection = std::make_shared<QMetaObject::Connection>();
*connection = QObject::connect(player, &Kuesa::AnimationPlayer::runningChanged, player, [this, player, connection] {
if (player->isRunning())
return;
Qt3DAnimation::QClock *c = player->clock();
const bool wasReversed = c->playbackRate() < 0.0;
c->setPlaybackRate(c->playbackRate() * -1.0f);
player->setNormalizedTime(c->playbackRate() > 0.0f ? 0.0f : 1.0f);
if (wasReversed) {
QObject::disconnect(*connection);
setSelectedPart(SelectablePart::NoPartSelected);
return;
}
player->start();
});
player->restart(delay);
}
void ScreenController::setSelectedPart(ScreenController::SelectablePart selectedPart)
{
if (selectedPart == m_selectedPart)
return;
m_selectedPart = selectedPart;
emit selectedPartChanged();
}
ScreenController::SelectablePart ScreenController::selectedPart() const
{
return m_selectedPart;
}
CompleteAnimationRunner::CompleteAnimationRunner(ScreenController *parent,
Kuesa::AnimationPlayer *p,
const CompleteAnimationRunner::Callback &callback,
float speed)
: QObject(parent)
{
if (!p->isRunning()) {
callback();
return;
}
p->setLoopCount(p->currentLoop() + 1);
ensureClockOnAnimationPlayer(p);
p->clock()->setPlaybackRate(speed);
QMetaObject::Connection *c = new QMetaObject::Connection;
auto singleShot = [=](bool running) {
if (!running) {
disconnect(*c);
callback();
deleteLater();
}
};
*c = QObject::connect(p, &Kuesa::AnimationPlayer::runningChanged, this, singleShot);
}
void ScreenController::setMode(ScreenController::Mode mode)
{
if (m_mode == mode)
return;
m_mode = mode;
emit modeChanged(mode);
switch (m_mode) {
case Mode::GuidedDrillingMode: {
new CompleteAnimationRunner(
this,
m_cameraAnimationPlayer,
[this]() {
m_mainViewConfiguration->setLayerNames({ QStringLiteral("LayerDevice"), QStringLiteral("LayerTools"), QStringLiteral("LayerEnv") });
m_mainViewConfiguration->setCameraName(QStringLiteral("CamTransition"));
reset();
nextStep();
},
10.0f);
break;
}
case Mode::UserManualMode: {
new CompleteAnimationRunner(
this,
m_cameraAnimationPlayer,
[this]() {
m_mainViewConfiguration->setCameraName(QStringLiteral("CamOrbitCenter.CamOrbit"));
m_mainViewConfiguration->setLayerNames({ QStringLiteral("LayerDevice"), QStringLiteral("LayerTools"), QStringLiteral("LayerEnv") });
reset();
},
10.0f);
break;
}
case Mode::StatusMode: {
m_cameraAnimationPlayer->setLoopCount(Kuesa::AnimationPlayer::Infinite);
m_cameraAnimationPlayer->clock()->setPlaybackRate(1.0f);
m_cameraAnimationPlayer->restart(true);
m_mainViewConfiguration->setCameraName(QStringLiteral("CamOrbitCenter.CamOrbit"));
m_mainViewConfiguration->setLayerNames({ QStringLiteral("LayerDevice"), QStringLiteral("LayerEnv") });
break;
}
default:
break;
}
}
ScreenController::Mode ScreenController::mode() const
{
return m_mode;
}
void ScreenController::setBit(ScreenController::Bit bit)
{
if (bit == m_bit)
return;
m_bit = bit;
emit bitChanged();
if (m_bit == Bit::None) {
setDrillMode(DrillMode::None);
setDrillingMaterial(MaterialType::None);
return;
}
const bool isScrewDriving = std::find(std::begin(screwDriverBits), std::end(screwDriverBits), bit) != std::end(screwDriverBits);
if (isScrewDriving) {
setDrillMode(ScreenController::DrillMode::Screw);
setDrillingMaterial(ScreenController::MaterialType::None);
return;
}
setDrillMode(ScreenController::DrillMode::Drill);
const bool isMetalBit = std::find(std::begin(metalDrillingBits), std::end(metalDrillingBits), bit) != std::end(metalDrillingBits);
if (isMetalBit) {
setDrillingMaterial(ScreenController::MaterialType::Metal);
} else {
const bool isWoodBit = std::find(std::begin(woodDrillingBits), std::end(woodDrillingBits), bit) != std::end(woodDrillingBits);
if (isWoodBit) {
setDrillingMaterial(ScreenController::MaterialType::Wood);
} else {
setDrillingMaterial(ScreenController::MaterialType::Concrete);
}
}
}
ScreenController::Bit ScreenController::bit() const
{
return m_bit;
}
void ScreenController::setDrillMode(ScreenController::DrillMode mode)
{
if (mode == m_drillingMode)
return;
m_drillingMode = mode;
emit drillModeChanged();
}
ScreenController::DrillMode ScreenController::drillingMode() const
{
return m_drillingMode;
}
void ScreenController::setDrillingMaterial(ScreenController::MaterialType material)
{
if (material == m_drillingMaterial)
return;
m_drillingMaterial = material;
emit drillingMaterialChanged();
}
ScreenController::MaterialType ScreenController::drillingMaterial() const
{
return m_drillingMaterial;
}
ScreenController::Step ScreenController::guidedDrillingStep() const
{
return m_drillingStep;
}
ScreenController::Step ScreenController::nextStep()
{
if (mode() != Mode::GuidedDrillingMode || m_steppedPlayer->isRunning())
return m_drillingStep;
auto findNextStep = [this]() -> Step {
return Step(int(m_drillingStep) + 1);
};
if (m_drillingStep < Step::CompletionStep) {
m_drillingStep = findNextStep();
emit guidedDrillingStepChanged();
m_animationClock->setPlaybackRate(1.0);
m_steppedPlayer->start();
} else {
m_steppedPlayer->setNormalizedTime(0.0f);
setMode(Mode::UserManualMode);
}
return m_drillingStep;
}
ScreenController::Step ScreenController::reset()
{
setBit(Bit::None);
m_drillingStep = Step::None;
emit guidedDrillingStepChanged();
m_steppedPlayer->setNormalizedTime(0.0);
return m_drillingStep;
}
QString ScreenController::bitName(ScreenController::Bit bit)
{
switch (bit) {
case Bit::None:
return QLatin1String("");
case Bit::Drill1:
return QStringLiteral("Metal drill 1");
case Bit::Drill2:
return QStringLiteral("Metal drill 2");
case Bit::Drill3:
return QStringLiteral("Wood drill 1");
case Bit::Drill4:
return QStringLiteral("Wood drill 2");
case Bit::Drill5:
return QStringLiteral("Concrete drill 1");
case Bit::Drill6:
return QStringLiteral("Concrete drill 2");
case Bit::ScrewHex:
return QStringLiteral("Hex");
case Bit::ScrewHexMedium:
return QStringLiteral("Medium hex");
case Bit::ScrewHexSmall:
return QStringLiteral("Small hex");
case Bit::ScrewHexTiny:
return QStringLiteral("Tiny hex");
case Bit::ScrewTorx:
return QStringLiteral("Torx");
case Bit::ScrewTorxMedium:
return QStringLiteral("Medium Torx");
case Bit::ScrewTorxSmall:
return QStringLiteral("Small Torx");
case Bit::ScrewTorxTiny:
return QStringLiteral("Tiny Torx");
case Bit::ScrewPhilips:
return QStringLiteral("Philips");
case Bit::ScrewPhilipsMedium:
return QStringLiteral("Medium Philips");
case Bit::ScrewPhilipsSmall:
return QStringLiteral("Small Philips");
case Bit::ScrewFlat:
return QStringLiteral("Flat");
case Bit::ScrewFlatMedium:
return QStringLiteral("Medium flat");
case Bit::ScrewFlatSmall:
return QStringLiteral("Small flat");
}
Q_UNREACHABLE();
}
float ScreenController::positionOnCameraOrbit() const
{
return m_cameraAnimationPlayer->normalizedTime();
}
void ScreenController::setPositionOnCameraOrbit(float p)
{
m_cameraAnimationPlayer->setNormalizedTime(p);
}
void ScreenController::updateSceneConfiguration()
{
hideDetailView();
switch (m_selectedPart) {
case SelectablePart::Trigger: {
showDetailView(QStringLiteral("CamTrigger"));
playAnimationBackAndForth(m_triggerAnimationPlayer, 750);
break;
}
case SelectablePart::Clutch: {
showDetailView(QStringLiteral("CamChuck"));
break;
}
case SelectablePart::Chuck: {
showDetailView(QStringLiteral("CamChuck"));
playAnimationBackAndForth(m_toolInOutAnimationPlayer);
break;
}
case SelectablePart::DirectionSwitch: {
showDetailView(QStringLiteral("CamDirectionSwitch"));
playAnimationBackAndForth(m_directionSwitchAnimationPlayer, 750);
break;
}
case SelectablePart::BatteryPack: {
showDetailView(QStringLiteral("CamBattery"));
playAnimationBackAndForth(m_batteryInOutAnimationPlayer, 750);
break;
}
case SelectablePart::NoPartSelected: {
break;
}
default:
Q_UNREACHABLE();
};
}
void ScreenController::showDetailView(const QString &cameraName)
{
m_detailViewConfiguration->setViewportRect({ 0.7f, 0.0f, 0.3f, 0.3f });
m_detailViewConfiguration->setCameraName(cameraName);
}
void ScreenController::hideDetailView()
{
m_detailViewConfiguration->setViewportRect({ 0.7f, 0.0f, 0.0f, 0.0f });
}
void ScreenController::loadDrillBit()
{
Kuesa::SceneEntity *sceneEntity = sceneConfiguration()->sceneEntity();
if (!sceneEntity)
return;
Qt3DCore::QEntity *drillBitHolder = sceneEntity->entity(QStringLiteral("Drill.DrillAxis.DrillHelper.ToolHelper"));
if (drillBitHolder) {
if (m_insertedDrillBit) {
m_insertedDrillBit->setParent(m_originalDrillBitParent);
m_insertedDrillBit->removeComponent(m_insertedDrillBitTranform);
m_insertedDrillBit->addComponent(m_originalDrillBitTransform);
m_insertedDrillBit = nullptr;
}
if (m_bit == Bit::None)
return;
m_insertedDrillBit = sceneEntity->entity(gltfBitName(m_bit));
m_insertedDrillBit->setParent(drillBitHolder);
m_originalDrillBitTransform = m_insertedDrillBit->componentsOfType<Qt3DCore::QTransform>().at(0);
m_insertedDrillBit->removeComponent(m_originalDrillBitTransform);
m_insertedDrillBit->addComponent(m_insertedDrillBitTranform);
}
}
void ScreenController::setPartLabelNames()
{
Kuesa::SceneEntity *sceneEntity = sceneConfiguration()->sceneEntity();
if (!sceneEntity)
return;
for (QObject *obj : qAsConst(m_partLabels)) {
PartLabel *label = qobject_cast<PartLabel *>(obj);
Qt3DCore::QEntity *labelEntity = sceneEntity->entity(label->nodeName());
if (labelEntity != nullptr)
label->setLabelName(labelEntity->property("text").toString());
}
}
void ScreenController::addObjectPickersOnBit()
{
Kuesa::SceneEntity *sceneEntity = sceneConfiguration()->sceneEntity();
if (!sceneEntity)
return;
for (const auto bit : bits) {
Qt3DCore::QEntity *drillBit = sceneEntity->entity(gltfBitName(bit));
if (!m_originalDrillBitParent)
m_originalDrillBitParent = drillBit->parentEntity();
if (drillBit->componentsOfType<Qt3DRender::QObjectPicker>().empty()) {
Qt3DRender::QObjectPicker *picker = new Qt3DRender::QObjectPicker();
picker->setHoverEnabled(true);
QObject::connect(picker, &Qt3DRender::QObjectPicker::clicked, this, [this, bit] {
if (mode() != Mode::GuidedDrillingMode)
setMode(Mode::GuidedDrillingMode);
else
setBit(bit);
});
drillBit->addComponent(picker);
}
}
}
PartLabel::PartLabel(const QString &nodeName,
const ScreenController::SelectablePart part,
Kuesa::TransformTracker *tracker,
QObject *parent)
: QObject(parent)
, m_nodeName(nodeName)
, m_part(part)
, m_tracker(tracker)
{
QObject::connect(tracker, &Kuesa::TransformTracker::screenPositionChanged,
this, &PartLabel::positionChanged);
}
QPointF PartLabel::position() const
{
return m_tracker->screenPosition();
}
QString PartLabel::labelName() const
{
return m_labelName;
}
ScreenController::SelectablePart PartLabel::part() const
{
return m_part;
}
QString PartLabel::nodeName() const
{
return m_nodeName;
}
void PartLabel::setLabelName(const QString &labelName)
{
if (labelName == m_labelName)
return;
m_labelName = labelName;
emit labelNameChanged();
}
QObjectList ScreenController::partLabels() const
{
return m_partLabels;
}