summaryrefslogtreecommitdiffstats
path: root/tests/manual/qardboard
diff options
context:
space:
mode:
Diffstat (limited to 'tests/manual/qardboard')
-rw-r--r--tests/manual/qardboard/CMakeLists.txt90
-rw-r--r--tests/manual/qardboard/Info.plist35
-rw-r--r--tests/manual/qardboard/PlacedEntity.qml35
-rw-r--r--tests/manual/qardboard/QardboardCamera.qml89
-rw-r--r--tests/manual/qardboard/QardboardFrameGraph.qml43
-rw-r--r--tests/manual/qardboard/QardboardRootEntity.qml33
-rw-r--r--tests/manual/qardboard/QardboardScene3D.qml21
-rw-r--r--tests/manual/qardboard/abstractdeviceorientation.cpp65
-rw-r--r--tests/manual/qardboard/abstractdeviceorientation.h49
-rw-r--r--tests/manual/qardboard/dummydeviceorientation.cpp26
-rw-r--r--tests/manual/qardboard/dummydeviceorientation.h21
-rw-r--r--tests/manual/qardboard/iosdeviceorientation.h21
-rw-r--r--tests/manual/qardboard/iosdeviceorientation.mm30
-rw-r--r--tests/manual/qardboard/iosdeviceorientation_p.h16
-rw-r--r--tests/manual/qardboard/iosdeviceorientation_p.mm105
-rw-r--r--tests/manual/qardboard/main.cpp38
-rw-r--r--tests/manual/qardboard/main.qml158
-rw-r--r--tests/manual/qardboard/resources.qrc10
18 files changed, 885 insertions, 0 deletions
diff --git a/tests/manual/qardboard/CMakeLists.txt b/tests/manual/qardboard/CMakeLists.txt
new file mode 100644
index 000000000..c03fb413f
--- /dev/null
+++ b/tests/manual/qardboard/CMakeLists.txt
@@ -0,0 +1,90 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qardboard LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(Qt6 REQUIRED COMPONENTS 3DCore 3DExtras 3DInput 3DQuick 3DQuickExtras 3DRender Core Gui Qml Quick)
+
+qt_add_executable(qardboard
+ abstractdeviceorientation.cpp abstractdeviceorientation.h
+ dummydeviceorientation.cpp dummydeviceorientation.h
+ main.cpp
+)
+
+set_target_properties(qardboard PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(qardboard PUBLIC
+ Qt::3DCore
+ Qt::3DExtras
+ Qt::3DInput
+ Qt::3DQuick
+ Qt::3DQuickExtras
+ Qt::3DRender
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+)
+
+# Resources:
+set(resources_resource_files
+ "PlacedEntity.qml"
+ "QardboardCamera.qml"
+ "QardboardFrameGraph.qml"
+ "QardboardRootEntity.qml"
+ "QardboardScene3D.qml"
+ "main.qml"
+)
+
+qt6_add_resources(qardboard "resources"
+ PREFIX
+ "/"
+ FILES
+ ${resources_resource_files}
+)
+
+set(obj_resource_files
+ "../exampleresources/assets/obj/ball.obj"
+ "../exampleresources/assets/obj/material-sphere.obj"
+ "../exampleresources/assets/obj/plane-10x10.obj"
+ "../exampleresources/assets/obj/toyplane.obj"
+ "../exampleresources/assets/obj/trefoil.obj"
+)
+
+qt6_add_resources(qardboard "obj"
+ PREFIX
+ "/"
+ BASE
+ "../exampleresources"
+ FILES
+ ${obj_resource_files}
+)
+
+if(IOS)
+ target_sources(qardboard PUBLIC
+ iosdeviceorientation.h iosdeviceorientation.mm iosdeviceorientation_p.h iosdeviceorientation_p.mm
+ )
+
+ target_link_libraries(qardboard PUBLIC
+ Qt::Sensors
+ Qt::SensorsPrivate
+ )
+endif()
+
+install(TARGETS qardboard
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/qardboard/Info.plist b/tests/manual/qardboard/Info.plist
new file mode 100644
index 000000000..26c5fe5e2
--- /dev/null
+++ b/tests/manual/qardboard/Info.plist
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleIconFile</key>
+ <string></string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleGetInfoString</key>
+ <string>Created by Qt/QMake</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleExecutable</key>
+ <string>qardboard</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.kdab.${PRODUCT_NAME:rfc1034identifier}</string>
+ <key>CFBundleDisplayName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleName</key>
+ <string>${PRODUCT_NAME}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/tests/manual/qardboard/PlacedEntity.qml b/tests/manual/qardboard/PlacedEntity.qml
new file mode 100644
index 000000000..075bfd48d
--- /dev/null
+++ b/tests/manual/qardboard/PlacedEntity.qml
@@ -0,0 +1,35 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import Qt3D.Core 2.0
+import Qt3D.Render 2.0
+import Qt3D.Extras 2.0
+
+Entity {
+ id: root
+ property double angle: 0
+ property double radius: 10
+ property var mesh
+ property alias color: material.diffuse
+ property vector3d center: Qt.vector3d(0, 0, 0)
+
+ components: [
+ Transform {
+ id: tr
+ matrix: {
+ var ra = 0// (root.angle) * Math.PI / 180
+ var m = Qt.matrix4x4()
+ m.rotate(root.angle, Qt.vector3d(0, 1, 0))
+ m.translate(Qt.vector3d(root.center.x + root.radius * Math.sin(ra), root.center.y, root.center.z - root.radius * Math.cos(ra)))
+ return m
+ }
+ }
+ ]
+
+ Entity {
+ PhongMaterial {
+ id: material
+ }
+ components: [ material, mesh ]
+ }
+}
diff --git a/tests/manual/qardboard/QardboardCamera.qml b/tests/manual/qardboard/QardboardCamera.qml
new file mode 100644
index 000000000..f20a18078
--- /dev/null
+++ b/tests/manual/qardboard/QardboardCamera.qml
@@ -0,0 +1,89 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import Qt3D.Core 2.0
+import Qt3D.Render 2.0
+import Qardboard 1.0
+
+Entity {
+ id: root
+ property real convergence: 2000.0
+ property real eyeSeparation: 35.0
+ property real aspectRatio: _window.width / _window.height
+ property real fieldOfView: 60.0
+ property real nearPlane: 1.
+ property real farPlane: 1000.0
+
+ property vector3d position: Qt.vector3d(0.0, 0.0, 0.0)
+ property vector3d viewDirection: Qt.vector3d(0.0, 0.0, 1.0)
+ property vector3d viewUp: Qt.vector3d(0.0, 1.0, 0.0)
+ property alias viewMatrix: eyeTransform.matrix
+
+ readonly property real _fov2: Math.tan(fieldOfView * Math.PI / 180 * 0.5)
+ readonly property real top: nearPlane * _fov2
+ readonly property real a: aspectRatio * _fov2 * convergence
+
+ DeviceOrientation {
+ id: orientation
+ enabled: true
+ }
+
+ CameraLens {
+ id: leftEyeLens
+ projectionType: CameraLens.FrustumProjection
+ nearPlane : root.nearPlane
+ farPlane : root.farPlane
+ left: -(a - eyeSeparation * 0.5) * nearPlane / convergence
+ right: (a + eyeSeparation * 0.5) * nearPlane / convergence
+ top: root.top
+ bottom: -root.top
+ }
+
+ CameraLens {
+ id: rightEyeLens
+ projectionType: CameraLens.FrustumProjection
+ nearPlane : root.nearPlane
+ farPlane : root.farPlane
+ left: -(a + eyeSeparation * 0.5) * nearPlane / convergence
+ right: (a - eyeSeparation * 0.5) * nearPlane / convergence
+ top: root.top
+ bottom: -root.top
+ }
+
+ Transform {
+ id: eyeTransform
+
+ function computeMatrix(roll, pitch, yaw) {
+ var n = Qt.matrix4x4()
+ n.rotate(-roll, Qt.vector3d(1, 0, 0))
+ n.rotate(yaw, Qt.vector3d(0, 1, 0))
+ var vd = n.times(stereoCamera.viewDirection)
+ var vu = n.times(stereoCamera.viewUp)
+ var vc = stereoCamera.position.minus(vd)
+
+ var m = Qt.matrix4x4();
+ m.translate(stereoCamera.position)
+ var zAxis = stereoCamera.position.minus(vc).normalized()
+ var xAxis = vu.crossProduct(zAxis).normalized();
+ var yAxis = zAxis.crossProduct(xAxis);
+ var r = Qt.matrix4x4(xAxis.x, yAxis.x, zAxis.x, 0,
+ xAxis.y, yAxis.y, zAxis.y, 0,
+ xAxis.z, yAxis.z, zAxis.z, 0,
+ 0, 0, 0, 1)
+ return m.times(r);
+ }
+
+ matrix: computeMatrix(orientation.roll, orientation.pitch, orientation.yaw)
+ }
+
+ components: [ eyeTransform ]
+
+ property Entity leftCamera: Entity {
+ components: [ leftEyeLens ]
+ }
+
+ property Entity rightCamera: Entity {
+ id: rightCameraEntity
+ components: [ rightEyeLens ]
+ }
+}
diff --git a/tests/manual/qardboard/QardboardFrameGraph.qml b/tests/manual/qardboard/QardboardFrameGraph.qml
new file mode 100644
index 000000000..9379b949a
--- /dev/null
+++ b/tests/manual/qardboard/QardboardFrameGraph.qml
@@ -0,0 +1,43 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import Qt3D.Core 2.0
+import Qt3D.Render 2.0
+
+Viewport {
+ property alias leftCamera: leftCameraSelector.camera
+ property alias rightCamera: rightCameraSelector.camera
+ property alias window: surfaceSelector.surface
+
+ RenderSurfaceSelector {
+ id: surfaceSelector
+
+ // ColorMask is reset by default
+ // By default reset to the default if not specified
+ ClearBuffers {
+ clearColor: "white"
+ buffers: ClearBuffers.ColorDepthBuffer
+ NoDraw {} // We just want to clear the buffers
+ }
+
+ // Draw with left eye
+ Viewport {
+ id: leftViewport
+ normalizedRect: Qt.rect(0, 0, .5, 1)
+
+ CameraSelector {
+ id: leftCameraSelector
+ }
+ }
+
+ // Draw with right eye
+ Viewport {
+ id: rightViewport
+ normalizedRect: Qt.rect(.5, 0, .5, 1)
+
+ CameraSelector {
+ id: rightCameraSelector
+ }
+ }
+ }
+}
diff --git a/tests/manual/qardboard/QardboardRootEntity.qml b/tests/manual/qardboard/QardboardRootEntity.qml
new file mode 100644
index 000000000..641341073
--- /dev/null
+++ b/tests/manual/qardboard/QardboardRootEntity.qml
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import Qt3D.Core 2.0
+import Qt3D.Render 2.0
+import Qt3D.Input 2.0
+
+Entity {
+ id: root
+ property alias fieldOfView: stereoCamera.fieldOfView
+ property alias cameraPosition: stereoCamera.position
+ property alias cameraDirection: stereoCamera.viewDirection
+ property alias headlightDirection: headLight.worldDirection
+
+ components: [
+ RenderSettings {
+ activeFrameGraph: QardboardFrameGraph {
+ id: stereoFrameGraph
+ leftCamera: stereoCamera.leftCamera
+ rightCamera: stereoCamera.rightCamera
+ }
+ },
+ InputSettings { },
+ DirectionalLight {
+ id: headLight
+ worldDirection: stereoCamera.viewDirection.times(-1)
+ }
+ ]
+
+ QardboardCamera {
+ id: stereoCamera
+ }
+}
diff --git a/tests/manual/qardboard/QardboardScene3D.qml b/tests/manual/qardboard/QardboardScene3D.qml
new file mode 100644
index 000000000..827eb4f88
--- /dev/null
+++ b/tests/manual/qardboard/QardboardScene3D.qml
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick 2.0
+import QtQuick.Scene3D 2.0
+
+import Qt3D.Core 2.0
+import Qt3D.Render 2.0
+import Qt3D.Input 2.0
+import Qt3D.Extras 2.0
+
+Scene3D {
+ id: scene3d
+ focus: true
+ aspects: ["input", "logic"]
+ cameraAspectRatioMode: Scene3D.UserAspectRatio
+ multisample: true
+
+ QardboardRootEntity {
+ }
+}
diff --git a/tests/manual/qardboard/abstractdeviceorientation.cpp b/tests/manual/qardboard/abstractdeviceorientation.cpp
new file mode 100644
index 000000000..eefeecddb
--- /dev/null
+++ b/tests/manual/qardboard/abstractdeviceorientation.cpp
@@ -0,0 +1,65 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "abstractdeviceorientation.h"
+
+#include <QDateTime>
+#include <QDebug>
+#include <QtMath>
+#include <QScreen>
+
+
+AbstractDeviceOrientation::AbstractDeviceOrientation(QObject *parent)
+ : QObject(parent)
+ , m_roll(0)
+ , m_pitch(0)
+ , m_yaw(0)
+ , m_enabled(false)
+{
+}
+
+bool AbstractDeviceOrientation::enabled() const
+{
+ return m_enabled;
+}
+
+void AbstractDeviceOrientation::stop()
+{
+ if (m_enabled) {
+ m_enabled = false;
+ emit enabledChanged(m_enabled);
+ }
+}
+
+void AbstractDeviceOrientation::setRoll(qreal v)
+{
+ if (v != m_roll) {
+ m_roll = v;
+ emit rollChanged(v);
+ }
+}
+
+void AbstractDeviceOrientation::setPitch(qreal v)
+{
+ if (v != m_pitch) {
+ m_pitch = v;
+ emit pitchChanged(v);
+ }
+}
+
+void AbstractDeviceOrientation::setYaw(qreal v)
+{
+ if (v != m_yaw) {
+ m_yaw = v;
+ emit yawChanged(v);
+ }
+}
+
+void AbstractDeviceOrientation::setEnabled(bool v)
+{
+ if (v && !m_enabled) {
+ start();
+ } else if (!v && m_enabled) {
+ stop();
+ }
+}
diff --git a/tests/manual/qardboard/abstractdeviceorientation.h b/tests/manual/qardboard/abstractdeviceorientation.h
new file mode 100644
index 000000000..0d1e34063
--- /dev/null
+++ b/tests/manual/qardboard/abstractdeviceorientation.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef ABSTRACT_DEVICEORIENTATION_H
+#define ABSTRACT_DEVICEORIENTATION_H
+
+#include <QObject>
+
+class AbstractDeviceOrientation : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(qreal roll READ roll WRITE setRoll NOTIFY rollChanged)
+ Q_PROPERTY(qreal pitch READ pitch WRITE setPitch NOTIFY pitchChanged)
+ Q_PROPERTY(qreal yaw READ yaw WRITE setYaw NOTIFY yawChanged)
+ Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged)
+
+public:
+ AbstractDeviceOrientation(QObject* parent = 0);
+
+ qreal roll() const { return m_roll; }
+ qreal pitch() const { return m_pitch; }
+ qreal yaw() const { return m_yaw; }
+
+ bool enabled() const;
+
+signals:
+ void rollChanged(qreal roll);
+ void pitchChanged(qreal pitch);
+ void yawChanged(qreal yaw);
+ void enabledChanged(bool);
+
+public slots:
+ virtual void setRoll(qreal v);
+ virtual void setPitch(qreal v);
+ virtual void setYaw(qreal v);
+ virtual void setEnabled(bool v);
+
+ virtual bool start() = 0; //!< Called to start the sensor services.
+ virtual void stop(); //!< Called to stop updates from the #CompassClino.
+
+protected:
+ qreal m_roll;
+ qreal m_pitch;
+ qreal m_yaw;
+ bool m_enabled;
+};
+
+
+#endif // ABSTRACT_DEVICEORIENTATION_H
diff --git a/tests/manual/qardboard/dummydeviceorientation.cpp b/tests/manual/qardboard/dummydeviceorientation.cpp
new file mode 100644
index 000000000..313522f46
--- /dev/null
+++ b/tests/manual/qardboard/dummydeviceorientation.cpp
@@ -0,0 +1,26 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "dummydeviceorientation.h"
+
+DummyDeviceOrientation::DummyDeviceOrientation(QObject *parent)
+ : AbstractDeviceOrientation(parent)
+{
+}
+
+bool DummyDeviceOrientation::start()
+{
+ if (!m_enabled) {
+ m_enabled = true;
+ emit enabledChanged(m_enabled);
+ }
+ return true;
+}
+
+void DummyDeviceOrientation::stop()
+{
+ if (m_enabled) {
+ m_enabled = false;
+ emit enabledChanged(m_enabled);
+ }
+}
diff --git a/tests/manual/qardboard/dummydeviceorientation.h b/tests/manual/qardboard/dummydeviceorientation.h
new file mode 100644
index 000000000..5b8c2bdef
--- /dev/null
+++ b/tests/manual/qardboard/dummydeviceorientation.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef DUMMY_DEVICEORIENTATION_H
+#define DUMMY_DEVICEORIENTATION_H
+
+#include "abstractdeviceorientation.h"
+
+class DummyDeviceOrientation : public AbstractDeviceOrientation
+{
+ Q_OBJECT
+public:
+ DummyDeviceOrientation(QObject* parent = 0);
+
+public slots:
+ bool start() override;
+ void stop() override;
+};
+
+
+#endif // DUMMY_DEVICEORIENTATION_H
diff --git a/tests/manual/qardboard/iosdeviceorientation.h b/tests/manual/qardboard/iosdeviceorientation.h
new file mode 100644
index 000000000..756dfd9e1
--- /dev/null
+++ b/tests/manual/qardboard/iosdeviceorientation.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef IOS_DEVICEORIENTATION_H
+#define IOS_DEVICEORIENTATION_H
+
+#include <abstractdeviceorientation.h>
+
+class iOSDeviceOrientation : public AbstractDeviceOrientation
+{
+ Q_OBJECT
+public:
+ iOSDeviceOrientation(QObject* parent = 0);
+
+public slots:
+ bool start() override;
+ void stop() override;
+};
+
+
+#endif // IOS_DEVICEORIENTATION_H
diff --git a/tests/manual/qardboard/iosdeviceorientation.mm b/tests/manual/qardboard/iosdeviceorientation.mm
new file mode 100644
index 000000000..0ac4e2489
--- /dev/null
+++ b/tests/manual/qardboard/iosdeviceorientation.mm
@@ -0,0 +1,30 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "iosdeviceorientation.h"
+#include "iosdeviceorientation_p.h"
+
+iOSDeviceOrientation::iOSDeviceOrientation(QObject *parent)
+ : AbstractDeviceOrientation(parent)
+{
+ [[iOSDeviceOrientationP instance] setHandler:this];
+}
+
+bool iOSDeviceOrientation::start()
+{
+ if (!m_enabled) {
+ [[iOSDeviceOrientationP instance] start];
+ m_enabled = true;
+ emit enabledChanged(m_enabled);
+ }
+ return true;
+}
+
+void iOSDeviceOrientation::stop()
+{
+ if (m_enabled) {
+ [[iOSDeviceOrientationP instance] stop];
+ m_enabled = false;
+ emit enabledChanged(m_enabled);
+ }
+}
diff --git a/tests/manual/qardboard/iosdeviceorientation_p.h b/tests/manual/qardboard/iosdeviceorientation_p.h
new file mode 100644
index 000000000..44c0b99a6
--- /dev/null
+++ b/tests/manual/qardboard/iosdeviceorientation_p.h
@@ -0,0 +1,16 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#import <Foundation/Foundation.h>
+#import <CoreLocation/CoreLocation.h>
+#import <CoreMotion/CoreMotion.h>
+
+
+@interface iOSDeviceOrientationP : NSObject <CLLocationManagerDelegate>
+@property (readonly) BOOL isActive;
+
++ (iOSDeviceOrientationP*)instance;
+- (void)setHandler:(iOSDeviceOrientation*) handler;
+- (void)start;
+- (void)stop;
+@end
diff --git a/tests/manual/qardboard/iosdeviceorientation_p.mm b/tests/manual/qardboard/iosdeviceorientation_p.mm
new file mode 100644
index 000000000..5034db4a6
--- /dev/null
+++ b/tests/manual/qardboard/iosdeviceorientation_p.mm
@@ -0,0 +1,105 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#import <Foundation/Foundation.h>
+#import <UIKit/UIKit.h>
+
+#include "iosdeviceorientation.h"
+#import "iosdeviceorientation_p.h"
+#include <QtCore/qmath.h>
+
+@interface QIOSMotionManager : NSObject {
+}
+
++ (CMMotionManager *)sharedManager;
+@end
+
+@interface iOSDeviceOrientationP()
+{
+ BOOL active;
+ iOSDeviceOrientation* handler;
+}
+
+@property (strong) CMMotionManager *motionManager;
+@end
+
+
+
+@implementation iOSDeviceOrientationP
+
+#define kUpdateFrequency 20.0
+
+
++ (iOSDeviceOrientationP*)instance
+{
+ static iOSDeviceOrientationP* _myInstance = 0;
+ if (0 == _myInstance)
+ _myInstance = [[iOSDeviceOrientationP alloc] init];
+ return _myInstance;
+}
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ self.motionManager = [QIOSMotionManager sharedManager]; // [[CMMotionManager alloc] init];
+ self.motionManager.deviceMotionUpdateInterval = 1. / kUpdateFrequency;
+
+ active = FALSE;
+ }
+ return self;
+}
+
+- (void)setHandler:(iOSDeviceOrientation*)h
+{
+ handler = h;
+}
+
+- (BOOL)isActive
+{
+ return active;
+}
+
+- (void)start
+{
+ // Motion updates
+ [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical
+ toQueue:[NSOperationQueue currentQueue]
+ withHandler: ^(CMDeviceMotion *motion, NSError *error) {
+ //CMAttitude *attitude = motion.attitude;
+ //NSLog(@"rotation rate = [%f, %f, %f]", attitude.pitch, attitude.roll, attitude.yaw);
+ if (error)
+ NSLog(@"%@", [error description]);
+ else
+ [self performSelectorOnMainThread:@selector(handleDeviceMotion:) withObject:motion waitUntilDone:YES];
+ }];
+ active = TRUE;
+}
+
+- (void)stop
+{
+ [_motionManager stopDeviceMotionUpdates];
+ active = FALSE;
+}
+
+- (void)handleDeviceMotion:(CMDeviceMotion*)motion
+{
+ if (!active)
+ return;
+
+ if (motion.magneticField.accuracy == CMMagneticFieldCalibrationAccuracyUncalibrated)
+ return;
+
+ // X: A pitch is a rotation around a lateral axis that passes through the device from side to side.
+ // Y: A roll is a rotation around a longitudinal axis that passes through the device from its top to bottom.
+ // Z: A yaw is a rotation around an axis that runs vertically through the device. It is perpendicular to the
+ // body of the device, with its origin at the center of gravity and directed toward the bottom of the device.
+
+ CMAttitude *attitude = motion.attitude;
+ handler->setRoll(90 - qRadiansToDegrees(attitude.roll));
+ handler->setPitch(qRadiansToDegrees(attitude.pitch));
+ handler->setYaw(qRadiansToDegrees(attitude.yaw));
+}
+
+@end
diff --git a/tests/manual/qardboard/main.cpp b/tests/manual/qardboard/main.cpp
new file mode 100644
index 000000000..7d5ac86c2
--- /dev/null
+++ b/tests/manual/qardboard/main.cpp
@@ -0,0 +1,38 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <Qt3DQuickExtras/qt3dquickwindow.h>
+#include <Qt3DQuick/QQmlAspectEngine>
+#include <QGuiApplication>
+#include <QQmlEngine>
+#include <QQmlContext>
+
+#ifdef Q_OS_IOS
+#include "iosdeviceorientation.h"
+#else
+#include "dummydeviceorientation.h"
+#endif
+
+int main(int argc, char* argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ Qt3DExtras::Quick::Qt3DQuickWindow view;
+
+#ifdef Q_OS_IOS
+ qmlRegisterType<iOSDeviceOrientation>("Qardboard", 1, 0, "DeviceOrientation");
+#else
+ qmlRegisterType<DummyDeviceOrientation>("Qardboard", 1, 0, "DeviceOrientation");
+#endif
+
+ view.engine()->qmlEngine()->rootContext()->setContextProperty(QStringLiteral("_window"), &view);
+ view.setSource(QUrl("qrc:/main.qml"));
+#ifdef Q_OS_IOS
+ view.showFullScreen();
+#else
+ view.resize(800, 400);
+ view.show();
+#endif
+
+ return app.exec();
+}
diff --git a/tests/manual/qardboard/main.qml b/tests/manual/qardboard/main.qml
new file mode 100644
index 000000000..63d2582b1
--- /dev/null
+++ b/tests/manual/qardboard/main.qml
@@ -0,0 +1,158 @@
+// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import Qt3D.Core 2.0
+import Qt3D.Render 2.0
+import Qt3D.Extras 2.0
+import QtQuick 2.4 as QQ2
+
+QardboardRootEntity {
+ id: root
+ cameraPosition: cameraController.position
+
+ Entity {
+ id: cameraController
+
+ function posAtAngle(a) {
+ return Qt.vector3d(cameraRadius * Math.cos(a), 0.0, cameraRadius * Math.sin(a))
+ }
+ function rotAtAngle(a) {
+ return a * 180 / Math.PI + 90
+ }
+
+ property real circleRotation: 0
+ readonly property real cameraRadius: obstaclesRepeater.radius
+ readonly property vector3d circlePosition: cameraController.posAtAngle(cameraController.circleRotation)
+ property vector3d position: planeTransform.translation.plus(Qt.vector3d(0, 0, 35))
+
+ QQ2.NumberAnimation {
+ target: cameraController
+ property: "circleRotation"
+ from: 0; to: Math.PI * 2
+ duration: 10000
+ loops: QQ2.Animation.Infinite
+ running: true
+ }
+ }
+
+ // AirPlane
+ Entity {
+ components: [
+ Mesh {
+ source: "assets/obj/toyplane.obj"
+ },
+ Transform {
+ id: planeTransform
+ property real rollAngle: 0.0
+ translation: cameraController.posAtAngle(cameraController.circleRotation)
+ rotation: fromAxesAndAngles(Qt.vector3d(1.0, 0.0, 0.0), planeTransform.rollAngle,
+ Qt.vector3d(0.0, -1.0, 0.0), cameraController.rotAtAngle(cameraController.circleRotation))
+ },
+ PhongMaterial {
+ shininess: 20.0
+ diffuse: "#ba1a02" // Inferno Orange
+ }
+ ]
+
+ QQ2.SequentialAnimation {
+ running: true
+ loops: QQ2.Animation.Infinite
+
+ QQ2.NumberAnimation {
+ target: planeTransform
+ property: "rollAngle"
+ from: -30; to: 45
+ duration: 1500
+ }
+ QQ2.NumberAnimation {
+ target: planeTransform
+ property: "rollAngle"
+ from: 45; to: -30
+ duration: 3500
+ }
+ }
+ }
+
+ // Cylinder
+ Entity {
+ property GeometryRenderer cylinder: CylinderMesh {
+ radius: 1
+ length: 3
+ rings: 100
+ slices: 20
+ }
+ property Transform transform: Transform {
+ id: cylinderTransform
+ property real theta: 0.0
+ property real phi: 0.0
+ rotation: fromEulerAngles(theta, phi, 0)
+ }
+ property Material phong: PhongMaterial {}
+
+ QQ2.ParallelAnimation {
+ loops: QQ2.Animation.Infinite
+ running: true
+ QQ2.SequentialAnimation {
+ QQ2.NumberAnimation {
+ target: cylinderTransform
+ property: "scale"
+ from: 5; to: 45
+ duration: 2000
+ easing.type: QQ2.Easing.OutInQuad
+ }
+ QQ2.NumberAnimation {
+ target: cylinderTransform
+ property: "scale"
+ from: 45; to: 5
+ duration: 2000
+ easing.type: QQ2.Easing.InOutQuart
+ }
+ }
+ QQ2.NumberAnimation {
+ target: cylinderTransform
+ property: "phi"
+ from: 0; to: 360
+ duration: 4000
+ }
+ QQ2.NumberAnimation {
+ target: cylinderTransform
+ property: "theta"
+ from: 0; to: 720
+ duration: 4000
+ }
+ }
+
+ components: [cylinder, transform, phong]
+ }
+
+ // Torus obsctacles
+ NodeInstantiator {
+ id: obstaclesRepeater
+ model: 4
+ readonly property real radius: 130.0;
+ readonly property real det: 1.0 / model
+ delegate: Entity {
+ components: [
+ TorusMesh {
+ radius: 35
+ minorRadius: 5
+ rings: 100
+ slices: 20
+ },
+ Transform {
+ id: transform
+ readonly property real angle: Math.PI * 2.0 * index * obstaclesRepeater.det
+ translation: Qt.vector3d(obstaclesRepeater.radius * Math.cos(transform.angle),
+ 0.0,
+ obstaclesRepeater.radius * Math.sin(transform.angle))
+ rotation: fromAxisAndAngle(Qt.vector3d(0.0, 1.0, 0.0), transform.angle * 180 / Math.PI)
+ },
+ PhongMaterial {
+ diffuse: Qt.rgba(Math.abs(Math.cos(transform.angle)), 204 / 255, 75 / 255, 1)
+ specular: "white"
+ shininess: 20.0
+ }
+ ]
+ }
+ }
+}
diff --git a/tests/manual/qardboard/resources.qrc b/tests/manual/qardboard/resources.qrc
new file mode 100644
index 000000000..fae6dc122
--- /dev/null
+++ b/tests/manual/qardboard/resources.qrc
@@ -0,0 +1,10 @@
+<RCC>
+ <qresource prefix="/">
+ <file>main.qml</file>
+ <file>QardboardCamera.qml</file>
+ <file>QardboardFrameGraph.qml</file>
+ <file>QardboardRootEntity.qml</file>
+ <file>QardboardScene3D.qml</file>
+ <file>PlacedEntity.qml</file>
+ </qresource>
+</RCC>