Updating a Qt Application

In Tangible Engine v2 we have migrated from a library that needs to be integrated into your project to a more flexible and modular service model with the goal of making the Tangible Engine easier to use. Additionally, changes to the core software allows for smoother tracking, better rotation, and more customization options to fine-tune recognition.

For clients who are currently using older versions of Tangible Engine with Qt Quick and want to upgrade to Tangible Engine v2, this guide will assist in making the process as simple as possible.

Step 1: Installing the Service

The Tangible Engine Service is a Windows service that runs silently in the background when the touch table is on, and waits for connection requests from client applications. In order to install the service, run the TE2.8_Installer.exe file. At the end of the installation process you will be prompted to select a default profile. Make sure to select the profile that correlates with the size of your Ideum touch table. (If you're using a 55 inch table, select).

The Tangible Engine Service will start running automatically and any time the computer is restarted.

Step 2: Importing your Patterns

In Tangible Engine v2 we have added engine customization information to the pattern files, which means that pattern files from previous versions of Tangible Engine are not compatible with v2, but can be re-imported and saved in the new format.

If you are using any of the nine standard tangibles, however, Tangible Engine will automatically recognize them as a part of the default profile. The best way to test this is to open the Visualizer by left-clicking on the system tray icon and pressing the "Launch Visualizer" button. When the Visualizer recognizes a tangible placed on the table, it will display information (see image below).
If you place your tangibles on the table and a tangible visual appears, it means the table recognizes your tangible already and there is no need to train. If, on the other hand, nothing appears, your tangible will need to be trained. In order to do this, you will need to launch the Tangible Engine Trainer application and create a new custom profile with all of the tangibles you intend to use. In the system tray interface, toggle the Table Profile button to Using Custom and select your newly created profile. For information about the Tangible Engine Trainer, see the Trainer documentation.

Step 3: Removing old versions of TE

Depending on which version of Tangible Engine you are upgrading from, there will be files in your project that are no longer necessary. In v1.6 or v1.5 (our latest versions prior to v2.0) there will only be a tangible_engine.h file as well as a .dll and .lib. These can all be removed. Most importantly, is the main.cpp file where Tangible Engine is being instantiated:

 qmlRegisterSingletonType<TangibleEngine>("Qt.TangibleEngine", 1, 5, "TangibleEngine", TangibleEngine::getInstance);
 
Depending on the version of TE, this line may vary. Comment out or remove any existing reference to the TangibleEngine class.

Step 4: Adding TE v2

In Tangible Engine v2, integration with your Qt Quick application is easier than ever. The main difference from old versions of Tangible Engine is that you no longer need to manage touch points going in and out of the engine. The TangibleClient class handles touch points and all communication with the service on its own; all you need to do is set it up.

In the Tangible Engine 2.8 install directory is a TangibleEngine directory that contains nine files:
  • TangibleEngine\
  • tangibleclient.cpp
  • tangibleclient.h
  • tangibledatastructures.h
  • tangibledatastructures.cpp
  • tangiblesimulator.h
  • tangiblesimulator.cpp
  • tcpthread.cpp
  • tcpthread.h
  • TangibleSimulator.qml
Add this folder to your Qt project and make sure that the files appear in your .pro file and in the project file list.
Next, navigate to your main.cpp file. Here you will need to add a few lines of code to create an instance of the TangibleClient, make it a context property of the QML engine, and initialize it with the application window so that it can handle touch points.

Create an instance of TangibleClient:


 TangibleClient* client = new TangibleClient("Localhost", 4949);
 
The second argument passed to the constructor needs to match the port used by the Tangible Engine Service. To check what port the Service is using, left-click on the system tray icon to open the interface. The port will be listed on that window as shown below. Note that TangibleClient's constructor has an optional third boolean argument to enable or disable Tangible Engine logging; the argument defaults to true.

Set the client instance as a QML context property:


 engine.rootContext()->setContextProperty("TangibleEngine", client);
 
This will allow you to reference Tangible Engine from QML.

Get a handle to the QWindow and pass it to the TangibleClient setup call:


  QWindow *main_window = qobject_cast<QWindow*>(engine.rootObjects().first());
  client->setup(main_window);
 
The TangibleClient needs a handle to the window so that it can receive any touch points and send those along to the service. From here, the TangibleClient instance will handle all communication with the Service on its own, in the background.

Below is the entire main.cpp file for the template project for reference:

  #include <QApplication>
  #include <QQmlApplicationEngine>
  #include <QFileInfo>
  #include <QMessageBox>
  #include <QQmlContext>
  #include <QQmlApplicationEngine>

  #include <TangibleEngine/tangibleclient.h>
  #include <TangibleEngine/tangiblesimulator.h>

  #define USE_TANGIBLE_SIMULATOR

  int main(int argc, char *argv[])
  {
      QApplication app(argc, argv);
      app.processEvents();
      app.setQuitOnLastWindowClosed(true);

      QQmlApplicationEngine engine;

  #ifdef USE_TANGIBLE_SIMULATOR
      TangibleSimulator * client = new TangibleSimulator();
  #else
      TangibleClient * client = new TangibleClient("Localhost", 4949);
  #endif

      qmlRegisterType<Tangible>("Tangibles",2,0,"Tangible");

      engine.rootContext()->setContextProperty("TangibleEngine", client);

      engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

      QWindow *main_window = qobject_cast<QWindow*>(engine.rootObjects().first());
      client->setup(main_window);

      return app.exec();
  }
  

Interfacing with TE in QML:

The TangibleClient class exposes two lists to QML: the list of patterns that the engine is searching for, and a list of tangibles that are currently recognized. The first list serves as a means to cross-reference recognized tangibles with a list of patterns. This allows you to have complete control of how the application reacts to unique tangibles. The second list is updated with every message received from the service and takes the form of a QAbstractListModel. In our example we use it as the model for a Repeater component.

main.qml:

  import QtQuick 2.9
  import QtQuick.Window 2.2

  Window {

      id: mainWindow

      visibility: "FullScreen"
      visible: true

      Item
      {
          focus: true
          Keys.onEscapePressed: Qt.quit()
      }

      Repeater
      {
          model: TangibleEngine.tangibles

          TangibleVisual {  tangible: model.tangible }
      }
  }
  
The TangibleVisual component is a simple visual representation of a tangible object that pulls the position, rotation, and other displayable information from the model.

TangibleVisual.qml:

  import QtQuick 2.0

  import Tangibles 2.0

  Item {
      property Tangible tangible

      readonly property string name: TangibleEngine.patterns[num].name
      readonly property real tangible_rotation: tangible.R * 180 / Math.PI
      readonly property int num: tangible.patternId

      readonly property color color: num >= 8 ? "black" : ["black","green","blue","red","magenta","darkblue","gold","gray","cyan"][num]

      id: root

      x: tangible.X
      y: tangible.Y

      ////////////////////////////////////////////////////////////////////////
      // Tangible Visual Elements (reticle, text)
      ////////////////////////////////////////////////////////////////////////
      SequentialAnimation
      {
          running: true
          loops: Animation.Infinite

          NumberAnimation
          {
              target: root
              property: "opacity"
              from: 1.0
              to: 0.5
              easing.type: Easing.InOutQuad
              duration: 1000
          }

          NumberAnimation
          {
              target: root
              property: "opacity"
              from: 0.5
              to: 1.0
              easing.type: Easing.InOutQuad
              duration: 1000
          }
      }

      Column
      {
          anchors.horizontalCenter: parent.horizontalCenter
          y: -ring.height/2-128

          Text
          {
              text: "ID: "+root.name
              color: root.color
              font.pixelSize: 32
              anchors.horizontalCenter: parent.horizontalCenter
          }

          Item
          {
              height: 32
              anchors.horizontalCenter: parent.horizontalCenter
              width: xpos.contentWidth+ypos.contentWidth+16

              Text
              {
                  id: xpos
                  anchors.right: parent.horizontalCenter
                  anchors.rightMargin: contentHeight/4
                  text: "X: "+root.x.toFixed(1)+", "
                  color: root.color
                  font.pixelSize: 32
              }

              Text
              {
                  id: ypos
                  anchors.left: parent.horizontalCenter
                  anchors.leftMargin: contentHeight/4
                  text: "Y: "+root.y.toFixed(1)
                  color: root.color
                  font.pixelSize: 32
              }
          }
          Text
          {
              text: "Rotation: "+root.tangible_rotation.toFixed(0)
              color: root.color
              font.pixelSize: 32
              anchors.horizontalCenter: parent.horizontalCenter
          }
      }

      Item
      {
          rotation: root.tangible_rotation

          Rectangle
          {
              id: ring
              anchors.centerIn: parent
              width: 350
              height: width
              radius: width/2
              color: "transparent"
              border.width: 2
              border.color: root.color
          }

          Rectangle
          {
              anchors.horizontalCenter: ring.right
              anchors.verticalCenter: ring.verticalCenter
              width: 30
              height: 2
              color: root.color
          }

          Rectangle
          {
              anchors.horizontalCenter: ring.left
              anchors.verticalCenter: ring.verticalCenter
              width: 30
              height: 2
              color: root.color
          }

          Rectangle
          {
              anchors.horizontalCenter: ring.horizontalCenter
              anchors.verticalCenter: ring.top
              width: 2
              height: 30
              color: root.color
          }

          Rectangle
          {
              anchors.horizontalCenter: ring.horizontalCenter
              anchors.verticalCenter: ring.bottom
              width: 2
              height: 30
              color: root.color
          }
      }
  }
As you can see, depending on which tangible is placed, a different color is used to represent that tangible.

Support

If you have any questions or encount any problems with upgrading your Tangible Engine project to v2, please submit a ticket at our support site.
Last Updated: 3/29/2022