This guide covers the implementation of two ADTF filters. One of them creates data for the sensor visualization. The second filter receives and displays the incoming data inside the main scene. The functional logic of this example is split into the following parts:
The Sensor Data Generator Filter creates data for the Sensor Visualization Filter. Two sensor data objects are continuously updated and sent to the visualization.
cmake_minimum_required(VERSION 3.10.0)
set(EXAMPLE_NAME sensor_data_generator_example)
set(PLUGIN_BINARY_OUTPUT_DIR "bin")
find_package(qt3ddisplay REQUIRED COMPONENTS base)
find_package(Qt5 COMPONENTS Core OpenGL Quick Qml QuickWidgets 3DCore 3DExtras 3DRender 3DInput)
project(${EXAMPLE_NAME} VERSION 1.0.0)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
# Adds the project to the Visual Studio solution, which when build
# creates a shared object called sensor_data_generator_example.adtfplugin
adtf_add_filter(${EXAMPLE_NAME}
${EXAMPLE_NAME}_filter.cpp
${EXAMPLE_NAME}_filter.h
qml/${EXAMPLE_NAME}_filter_resources.qrc
qml/${EXAMPLE_NAME}_filter_main.qml
)
set_target_properties(${EXAMPLE_NAME} PROPERTIES
AUTORCC true
AUTOUIC true)
target_link_libraries(${EXAMPLE_NAME} PRIVATE
adtf::qt3d::base
)
# Adds the INSTALL project to the Visual Studio solution, which when build
# copies our filter to the subdirectory given as the second argument into ${PLUGIN_BINARY_OUTPUT_DIR}
adtf_install_filter(${EXAMPLE_NAME} ${PLUGIN_BINARY_OUTPUT_DIR})
set_target_properties(${EXAMPLE_NAME} PROPERTIES FOLDER examples/guides)
# Generates a plugindescription for our filter
adtf_create_plugindescription(
TARGET
${EXAMPLE_NAME}
PLUGIN_SUBDIR
${PLUGIN_BINARY_OUTPUT_DIR}
)
Now use the CMake-GUI to fill in all the information required by CMake to create the Visual Studio solution.
Then open thesensor_data_generator_example
project in the solution explorer.
Just like before we continue by creating our sensor_data_generator_example_filter.h header file.
#pragma once
#include <qt3ddisplay/base/qt3d_base_filter.h>
// To implement a Filter which can be used in 3D-Display we subclass adtf::qt3d::cQt3DBaseFilter.
class cQt3DSensorDataGenerator : public adtf::qt3d::cQt3DBaseFilter
{
public:
// This macros provides some meta information about our Implementation.
// This will be exposed by the plugin class factory.
ADTF_CLASS_ID_NAME(cQt3DSensorDataGenerator,
"sensorDataGenerator.qt3d.cid",
"Qt3D Sensor Data Generator");
// Constructor
cQt3DSensorDataGenerator();
// Destructor
~cQt3DSensorDataGenerator() = default;
};
Now we need to write the sensor_data_generator_example_filter.cpp source file.
#include "sensor_data_generator_example_filter.h"
// For simplicity use the necessary namespaces.
using namespace adtf::ucom;
using namespace adtf::streaming;
using namespace adtf::services;
using namespace adtf::base;
using namespace adtf::mediadescription;
using namespace adtf::qt3d;
// The code behind the macro creates a plugin and the main entries to the plugin DLL or shared object.
// The cQt3DSensorDataGenerator class will be available through the plugins class factory.
ADTF_PLUGIN("Qt3D Data Generator", cQt3DSensorDataGenerator)
// The main QML file gets loaded from the base class.
cQt3DSensorDataGenerator::cQt3DSensorDataGenerator():
cQt3DBaseFilter(":/modules/Qt3DSensorDataGenerator/sensor_data_generator_example_filter_main.qml", false)
{
// sets a short description for the component
set_description(*this, "The sensor data generator example filter creates data for the sensor visualization filter.");
// set help link to jump to documentation from ADTF Configuration Editor
set_help_link(*this, "../doc/guides/tutorial_sensor_visualization_example.html");
}
Now we need to write the sensor_data_generator_example_filter_main.qml qml file.
// Import the Qt libaries.
import QtQuick 2.12
import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Input 2.12
import Qt3D.Extras 2.12
Item
{
id: root
// Use a property to create an output pin.
property var outputPin
// Define properties to store random sensor data.
property vector3d sensorPos
property vector3d rotation
property int angleCounter: 0
property int circleRoadwayDiameter: 5
// Use the onCompleted handler to create stream types and an output pin.
Component.onCompleted:
{
// The types object offers several methods to define stream types.
// Here we use the createDefinition call were we can build our output data description.
var colorType = types.createDefinition("tColor")
.add("nRed", "tUInt8")
.add("nGreen", "tUInt8")
.add("nBlue", "tUInt8")
.add("nAlpha", "tUInt8")
var vector3DType = types.createDefinition("t3DVector")
.add("fX", "tFloat64")
.add("fY", "tFloat64")
.add("fZ", "tFloat64")
var objectType = types.createDefinition("t3DSensorData")
.add("nIdx", "tUInt32")
.add("oColor", colorType)
.add("oPosition", vector3DType)
.add("oRange", "tFloat64")
.add("oRotation", vector3DType)
.add("oOpeningAngle", "tFloat64")
// Create an output pin.
root.outputPin = filter.createOutputPin("data", objectType);
// We connect a callback to the timer runner.
filter.createRunner("generate_sensor_data").trigger.connect(function(timestamp) {
if(outputPin) {
// Create a first object of type "t3DSensorData" and write it to the output pin.
outputPin.write(timestamp, {
nIdx: 0,
oColor: { nRed: 64, nGreen: 224, nBlue: 208, nAlpha: 200 },
oPosition: { fX: root.sensorPos.x, fY: root.sensorPos.y, fZ: root.sensorPos.z },
oRange: 3,
oRotation: { fX: 0, fY: 0, fZ: 0 },
oOpeningAngle: 360
})
// Create a second object of type "t3DSensorData" and write it to the output pin.
outputPin.write(timestamp, {
nIdx: 1,
oColor: { nRed: 255, nGreen: 130, nBlue: 25, nAlpha: 200 },
oPosition: { fX: root.sensorPos.x, fY: root.sensorPos.y, fZ: root.sensorPos.z },
oRange: 10,
oRotation: { fX: root.rotation.x, fY: root.rotation.y, fZ: root.rotation.z },
oOpeningAngle: 45
})
// We increment the angle counter each cycle.
incrementCounter()
// Based on the counter we calculate the center position of the sensor.
root.sensorPos = generatePosition(root.angleCounter)
// Based on the counter we calculate the rotation for the second sensor.
root.rotation = Qt.vector3d(0, root.angleCounter, 0)
}
})
}
// Returns a vector3d with the center position of a sensor.
// We use the diameter of the desired circle and create a track on the XZ plane.
function generatePosition(count) {
return Qt.vector3d(circleRoadwayDiameter * Math.sin(count / 180 * Math.PI), 0.25, circleRoadwayDiameter * Math.cos(count / 180 * Math.PI))
}
// Increments the counter by one.
// The counter gets reseted if 360 is reached.
function incrementCounter() {
if(root.angleCounter <= 360) {
root.angleCounter++
} else {
root.angleCounter = 1
}
}
}
Now we need to write the sensor_data_generator_example_filter_resources.qrc resources file. For detailed information about the Qt Resource System have a look at Qt Resources System.
<RCC>
<qresource prefix="/modules/Qt3DSensorDataGenerator">
<file>sensor_data_generator_example_filter_main.qml</file>
</qresource>
</RCC>
The Visualization Filter receives incoming data and visualizes the area which is covered by the respective sensor. A custom geometry which is part of the AdtfQt3DBase library based on the Qt GeometryRenderer is used to visualize the sensor area. A detailed guide how to write an own geometry will be provided in an upcoming version.
cmake_minimum_required(VERSION 3.10.0)
set(EXAMPLE_NAME sensor_visualization_example)
set(PLUGIN_BINARY_OUTPUT_DIR "bin")
find_package(qt3ddisplay REQUIRED COMPONENTS base qmlbase)
find_package(Qt5 COMPONENTS Core OpenGL Quick Qml QuickWidgets 3DCore 3DExtras 3DRender 3DInput)
project(${EXAMPLE_NAME} VERSION 1.0.0)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
# Adds the project to the Visual Studio solution, which when build
# creates a shared object called sensor_visualization_example.adtfplugin
adtf_add_filter(${EXAMPLE_NAME}
${EXAMPLE_NAME}_filter.cpp
${EXAMPLE_NAME}_filter.h
qml/${EXAMPLE_NAME}_filter_resources.qrc
qml/${EXAMPLE_NAME}_filter_main.qml
)
set_target_properties(${EXAMPLE_NAME} PROPERTIES
AUTORCC true
AUTOUIC true)
target_link_libraries(${EXAMPLE_NAME} PRIVATE
adtf::qt3d::base
Qt5::3DRender
)
# Adds the INSTALL project to the Visual Studio solution, which when build
# copies our filter to the subdirectory given as the second argument into ${PLUGIN_BINARY_OUTPUT_DIR}
adtf_install_filter(${EXAMPLE_NAME} ${PLUGIN_BINARY_OUTPUT_DIR})
set_target_properties(${EXAMPLE_NAME} PROPERTIES FOLDER examples/guides)
# Generates a plugindescription for our filter
adtf_create_plugindescription(
TARGET
${EXAMPLE_NAME}
PLUGIN_SUBDIR
${PLUGIN_BINARY_OUTPUT_DIR}
)
Now use the CMake-GUI to fill in all the information required by CMake to create the Visual Studio solution.
Then open thesensor_visualization_example
project in the solution explorer.
Just like before we continue by creating our sensor_visualization_example_filter.h header file.
#pragma once
#include <qt3ddisplay/base/qt3d_base_filter.h>
// To implement a Filter which can be used in 3D-Display we subclass adtf::qt3d::cQt3DBaseFilter.
class cQt3DSensorVisualization : public adtf::qt3d::cQt3DBaseFilter
{
public:
// This macros provides some meta information about our Implementation.
// This will be exposed by the plugin class factory.
ADTF_CLASS_ID_NAME(cQt3DSensorVisualization,
"sensorVisualization.qt3d.cid",
"Qt3D Sensor Visualization");
// Constructor
cQt3DSensorVisualization();
// Destructor
~cQt3DSensorVisualization() = default;
};
Now we need to write the sensor_visualization_example_filter.cpp source file.
#include "sensor_visualization_example_filter.h"
// For simplicity use the necessary namespaces.
using namespace adtf::ucom;
using namespace adtf::streaming;
using namespace adtf::services;
using namespace adtf::base;
using namespace adtf::mediadescription;
using namespace adtf::qt3d;
// The code behind the macro creates a plugin and the main entries to the plugin DLL or shared object.
// The cQt3DSensorVisualization class will be available through the plugins class factory.
ADTF_PLUGIN("Qt3D Sensor Visualization", cQt3DSensorVisualization)
// The main QML file gets loaded from the base class.
cQt3DSensorVisualization::cQt3DSensorVisualization():
cQt3DBaseFilter(":/modules/Qt3DSensorVisualization/sensor_visualization_example_filter_main.qml", false)
{
// sets a short description for the component
set_description(*this, "The sensor visualization example filter creates a plane 3D visualization for given sensor data.");
// set help link to jump to documentation from ADTF Configuration Editor
set_help_link(*this, "../doc/guides/tutorial_sensor_visualization_example.html");
}
Now we need to write the sensor_visualization_example_filter_main.qml qml file.
// Import the Qt libaries.
import QtQuick 2.12
import QtQml.Models 2.12
import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Input 2.12
import Qt3D.Extras 2.12
// Import the Qt3DBase library.
import AdtfQt3DBase 1.0
Item
{
id: root
property var sensorVisualizationDelegate: sensorVisualization
// Use a ListModel to store incoming data.
ListModel {
id: incomingSensorData
}
// Use the onCompleted handler to connect the filter to the incoming data and create an interface client.
Component.onCompleted: {
// Create an input pin.
const input = filter.createInputPin("sensorData")
// Store incoming data to ListModel.
input.sample.connect(function(sample) {
incomingSensorData.set(sample.data.nIdx, sample.data)
})
const qtshared = filter.createInterfaceClient("qtshared", "qtshared")
// Set the parent of your implementation to 'rootEntity' of the 3D-Display.
filter.connected.connect(function() {
if (qtshared.connected) {
sensorVisualizationRootEntity.parent = qtshared.getObject("rootEntity")
}
})
}
Entity {
id: sensorVisualizationRootEntity
// Set a name which is displayed in the VisualElementsView.
objectName: "sensorVisualization"
// The NodeInstantiator creates a sensor visualization component for each incoming dataset.
NodeInstantiator {
model: incomingSensorData
delegate: root.sensorVisualizationDelegate
}
}
Component {
id: sensorVisualization
// Add a custom Entity which consists of a Geometry, Transform and a Material component.
// We use the data which is stored in the ListModel.
Entity {
id: sensorVisualizationEntity
// Set an object name which is displayed in the VisualElementsView.
objectName: "Sensor " + (nIdx + 1)
// The GeometryRenderer provides the functionality to draw a custom geometry.
// Here we use the Qt3DSensorAreaGeometry which is part of the AdtfQt3DBase library.
GeometryRenderer {
id: geometryRenderer
primitiveType: GeometryRenderer.Triangles
vertexCount: sensorAreaGeometry.count
geometry: Qt3DSensorAreaGeometry {
id: sensorAreaGeometry
openingAngle: oOpeningAngle
range: oRange
}
}
// Use the Transform component to apply all incoming properties.
Transform {
id: transform
translation: convertVector3d(oPosition)
property vector3d rotationVec: convertVector3d(oRotation)
rotationX: rotationVec.x
rotationY: rotationVec.y
rotationZ: rotationVec.z
}
// Set the Material component of the entity and apply all incoming properties.
PhongAlphaMaterial {
id: material
alpha: oColor.nAlpha / 255.0
ambient: convertColor(oColor)
}
components: [geometryRenderer, transform, material]
}
}
// Converts the incoming data vector structure to a Qt.vector3d object.
function convertVector3d(vector) {
return Qt.vector3d(vector.fX, vector.fY, vector.fZ)
}
// Converts the incoming data vector structure to a Qt.rgba object.
function convertColor(color) {
return Qt.rgba(color.nRed / 255.0, color.nGreen / 255.0, color.nBlue / 255.0, 1.0)
}
}
Now we need to write the sensor_data_generator_example_filter_resources.qrc resources file. For detailed information about the Qt Resource System have a look at Qt Resources System.
<RCC>
<qresource prefix="/modules/Qt3DSensorVisualization">
<file>sensor_visualization_example_filter_main.qml</file>
</qresource>
</RCC>
Finally we need to build both Projects.
Now we want to integrate and test our filters in an ADTF Project
.
We can accomplish this using the Configuration Editor.
The following example also includes the Grid Filter.
Finally, we can run the session and visualize our data.
Have a look at the Index.