diff --git a/contrib/ar4dcops/app_android/README.md b/contrib/ar4dcops/app_android/README.md
new file mode 100644
index 000000000..4f013b53d
--- /dev/null
+++ b/contrib/ar4dcops/app_android/README.md
@@ -0,0 +1,13 @@
+# App-Android
+
+An Augmented Reality (AR) based app that runs on Android systems to aid data center maintenance and audit work. It obtains data from
+the back-end server Flowgate, which integrates all the information related to a specific data
+center, and displays necessary information onto real scenes" in the data center using AR
+technology.
+
+Note:
+
+Some problems remain before our project period ends. The app might not be very stable because of compability issues (the packages we
+use might have some conflicts with ARcore). It may run smoothly
+on some Android devices but can crash frequently on some other types of devices. Also, its features are not as comprehensive as the
+IOS version. We hope that future developers can try to improve the solution.
diff --git a/contrib/ar4dcops/app_android/augmentedimage/.gitignore b/contrib/ar4dcops/app_android/augmentedimage/.gitignore
new file mode 100644
index 000000000..863f89cd4
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/.gitignore
@@ -0,0 +1,13 @@
+# Android Studio configuration.
+*.iml
+.idea/
+#
+# # Gradle configuration.
+.gradle/
+build/
+#
+# # User configuration.
+local.properties
+#
+# # OS configurations.
+.DS_Store
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/.gitignore b/contrib/ar4dcops/app_android/augmentedimage/app/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/build.gradle b/contrib/ar4dcops/app_android/augmentedimage/app/build.gradle
new file mode 100644
index 000000000..266d04375
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/build.gradle
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 Google LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.google.ar.sceneform.samples.augmentedimage"
+
+ // Sceneform requires minSdkVersion >= 24.
+ minSdkVersion 24
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+ // Sceneform libraries use language constructs from Java 8.
+ // Add these compile options if targeting minSdkVersion < 26.
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.0.0'
+ implementation 'com.google.android.material:material:1.0.0'
+
+ // Use the Sceneform UX Package pre-built from Maven.
+ implementation "com.google.ar.sceneform.ux:sceneform-ux:1.15.0"
+ implementation 'com.google.mlkit:barcode-scanning:16.0.3'
+ implementation 'com.android.volley:volley:1.1.1'
+
+ // Use the Sceneform UX Package built from the source files included in the sceneformux folder.
+ //api project(":sceneformux")
+}
+
+apply plugin: 'com.google.ar.sceneform.plugin'
+
+sceneform.asset('sampledata/models/frame_lower_left.obj',
+ 'default',
+ 'sampledata/models/frame_lower_left.sfa',
+ 'src/main/assets/models/frame_lower_left')
+
+sceneform.asset('sampledata/models/frame_lower_right.obj',
+ 'default',
+ 'sampledata/models/frame_lower_right.sfa',
+ 'src/main/assets/models/frame_lower_right')
+
+sceneform.asset('sampledata/models/frame_upper_left.obj',
+ 'default',
+ 'sampledata/models/frame_upper_left.sfa',
+ 'src/main/assets/models/frame_upper_left')
+
+sceneform.asset('sampledata/models/frame_upper_right.obj',
+ 'default',
+ 'sampledata/models/frame_upper_right.sfa',
+ 'src/main/assets/models/frame_upper_right')
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/proguard-rules.pro b/contrib/ar4dcops/app_android/augmentedimage/app/proguard-rules.pro
new file mode 100644
index 000000000..45dc58a59
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /opt/android-sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_base.png b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_base.png
new file mode 100644
index 000000000..5fcfa6ea3
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_base.png differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.mtl b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.mtl
new file mode 100644
index 000000000..27ca7b066
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.mtl
@@ -0,0 +1,7 @@
+newmtl unlit_material
+illum 2
+Kd 0.00 0.00 0.00
+Ka 0.00 0.00 0.00
+Tf 1.00 1.00 1.00
+map_Kd frame_base.png
+Ni 1.00
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.obj b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.obj
new file mode 100644
index 000000000..d7856723e
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.obj
@@ -0,0 +1,420 @@
+mtllib frame_lower_left.mtl
+v 0.019500 0.001781 -0.019500
+v 0.019500 -0.000000 -0.019500
+v 0.009750 0.004591 -0.009750
+v 0.009027 0.005968 -0.009027
+v 0.007902 0.007060 -0.007902
+v 0.006484 0.007761 -0.006484
+v 0.003340 0.007761 -0.003340
+v 0.000000 0.000000 0.000000
+v 0.000000 0.009051 0.000000
+v 0.018467 0.001781 -0.018467
+v 0.014182 0.002906 -0.014182
+v 0.014636 0.002160 -0.014636
+v 0.010457 0.003749 -0.010457
+v 0.002438 0.009051 -0.002438
+v 0.011328 0.003131 -0.011328
+v 0.017860 0.002160 -0.017860
+v 0.016248 0.002488 -0.016248
+v 0.019500 -0.000000 -0.030469
+v 0.019500 0.001781 -0.030469
+v 0.018467 0.001781 -0.031502
+v 0.017860 0.002160 -0.032109
+v 0.016248 0.002488 -0.033721
+v 0.014636 0.002160 -0.035333
+v 0.014182 0.002906 -0.035787
+v 0.011328 0.003131 -0.038641
+v 0.010457 0.003749 -0.039512
+v 0.009750 0.004591 -0.040219
+v 0.009027 0.005968 -0.040942
+v 0.007902 0.007060 -0.042067
+v 0.006484 0.007761 -0.043485
+v 0.003340 0.007761 -0.046629
+v 0.002438 0.009051 -0.047530
+v -0.000000 0.009051 -0.049969
+v -0.000000 0.000000 -0.049969
+v 0.049969 0.000000 0.000000
+v 0.049969 0.009051 0.000000
+v 0.047530 0.009051 -0.002438
+v 0.046629 0.007761 -0.003340
+v 0.043485 0.007761 -0.006484
+v 0.042067 0.007060 -0.007902
+v 0.040941 0.005968 -0.009027
+v 0.040219 0.004591 -0.009750
+v 0.039512 0.003749 -0.010457
+v 0.038641 0.003131 -0.011328
+v 0.035787 0.002906 -0.014182
+v 0.035333 0.002160 -0.014636
+v 0.033721 0.002488 -0.016248
+v 0.032108 0.002160 -0.017860
+v 0.031502 0.001781 -0.018467
+v 0.030469 0.001781 -0.019500
+v 0.030469 -0.000000 -0.019500
+v 0.014182 0.000000 -0.035787
+v 0.009043 0.000000 -0.040926
+v 0.040925 0.000000 -0.009043
+v 0.035787 0.000000 -0.014182
+vt 0.145859 0.175636
+vt 0.155382 0.180635
+vt 0.155382 0.391434
+vt 0.145859 0.396434
+vt 0.138302 0.167849
+vt 0.138302 0.404220
+vt 0.128490 0.158038
+vt 0.128490 0.414032
+vt 0.106738 0.136285
+vt 0.106738 0.435784
+vt 0.212705 0.236748
+vt 0.215329 0.240946
+vt 0.215329 0.331125
+vt 0.212705 0.335323
+vt 0.201550 0.225593
+vt 0.201550 0.346477
+vt 0.190396 0.357632
+vt 0.190396 0.214438
+vt 0.177930 0.148151
+vt 0.398727 0.148152
+vt 0.393727 0.157675
+vt 0.182929 0.157674
+vt 0.170143 0.140594
+vt 0.406513 0.140594
+vt 0.160332 0.130782
+vt 0.416325 0.130783
+vt 0.138579 0.109030
+vt 0.438077 0.109030
+vt 0.250384 0.224766
+vt 0.326272 0.224766
+vt 0.326272 0.237088
+vt 0.250384 0.237088
+vt 0.132344 0.100107
+vt 0.444312 0.100107
+vt 0.363063 0.187527
+vt 0.213593 0.187527
+vt 0.187818 0.163503
+vt 0.388838 0.163504
+vt 0.382812 0.167778
+vt 0.193844 0.167778
+vt 0.239040 0.214997
+vt 0.337616 0.214997
+vt 0.333417 0.217621
+vt 0.243239 0.217621
+vt 0.227886 0.203842
+vt 0.348770 0.203843
+vt 0.216732 0.192688
+vt 0.359924 0.192688
+vt 0.115474 0.083237
+vt 0.461182 0.083237
+vt 0.115474 0.020617
+vt 0.461182 0.020617
+vt 0.080945 0.458889
+vt 0.018325 0.458889
+vt 0.018325 0.113180
+vt 0.080946 0.113180
+vt 0.097815 0.442019
+vt 0.097815 0.130050
+vt 0.161211 0.185524
+vt 0.161211 0.386545
+vt 0.165485 0.380519
+vt 0.165486 0.191551
+vt 0.185235 0.211300
+vt 0.185235 0.360770
+vt 0.222474 0.248091
+vt 0.222473 0.323980
+vt 0.234795 0.323980
+vt 0.234796 0.248091
+vt 0.247360 0.337045
+vt 0.250383 0.332209
+vt 0.264576 0.323980
+vt 0.244751 0.349895
+vt 0.264576 0.366359
+vt 0.247360 0.362743
+vt 0.241415 0.366359
+vt 0.264576 0.407313
+vt 0.239625 0.389107
+vt 0.234701 0.396049
+vt 0.227987 0.401681
+vt 0.217017 0.407440
+vt 0.208312 0.416409
+vt 0.202723 0.427711
+vt 0.202722 0.452768
+vt 0.264577 0.479383
+vt 0.192444 0.459951
+vt 0.192444 0.479383
+vt 0.250383 0.323980
+vt 0.462243 0.198405
+vt 0.481675 0.270537
+vt 0.455060 0.208683
+vt 0.409606 0.270537
+vt 0.430004 0.208683
+vt 0.418702 0.214272
+vt 0.409733 0.222978
+vt 0.403974 0.233947
+vt 0.398342 0.240662
+vt 0.391400 0.245586
+vt 0.368652 0.247375
+vt 0.368652 0.270537
+vt 0.365036 0.253321
+vt 0.352188 0.250711
+vt 0.326272 0.270537
+vt 0.339339 0.253321
+vt 0.334503 0.256343
+vt 0.326272 0.256343
+vt 0.481675 0.198405
+vn 0.766581 0.642148 0.000000
+vn 0.859115 0.511782 -0.000000
+vn 0.859116 0.511782 -0.000000
+vn 0.766582 0.642146 0.000000
+vn 0.517901 0.855441 -0.000000
+vn 0.517901 0.855441 -0.000000
+vn 0.061574 0.998103 -0.000000
+vn 0.061575 0.998103 -0.000000
+vn 0.000000 1.000000 -0.000000
+vn 0.000000 1.000000 -0.000000
+vn 0.240884 0.970554 0.000001
+vn 0.530016 0.847988 0.000002
+vn 0.530016 0.847988 0.000002
+vn 0.240885 0.970554 0.000001
+vn -0.113066 0.993588 -0.000000
+vn -0.113067 0.993587 -0.000000
+vn -0.199028 0.979994 -0.000000
+vn -0.199028 0.979994 -0.000000
+vn -0.000001 0.642157 -0.766573
+vn -0.000001 0.642157 -0.766573
+vn -0.000001 0.511787 -0.859112
+vn -0.000001 0.511787 -0.859113
+vn -0.000000 0.855445 -0.517893
+vn -0.000000 0.855445 -0.517893
+vn 0.000000 0.998102 -0.061576
+vn 0.000000 0.998102 -0.061576
+vn 0.000000 1.000000 -0.000000
+vn 0.000000 1.000000 -0.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.572793 -0.819700
+vn 0.000000 0.572793 -0.819700
+vn 0.000000 0.572793 -0.819700
+vn 0.000000 0.572793 -0.819700
+vn -0.000001 0.694850 -0.719155
+vn -0.000001 0.694849 -0.719155
+vn -0.000000 0.930015 -0.367521
+vn -0.000000 0.930014 -0.367523
+vn -0.000000 0.996920 -0.078425
+vn -0.000000 0.996920 -0.078425
+vn -0.000001 0.970555 -0.240881
+vn -0.000001 0.970555 -0.240881
+vn -0.000002 0.847990 -0.530013
+vn -0.000002 0.847990 -0.530013
+vn 0.000000 0.993588 0.113065
+vn 0.000000 0.993588 0.113064
+vn 0.000000 0.979994 0.199028
+vn 0.000000 0.979994 0.199028
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000000 -0.000000 1.000000
+vn 0.000006 1.000000 -0.000006
+vn 0.000006 1.000000 -0.000006
+vn -0.000000 1.000000 -0.000012
+vn -0.000000 1.000000 -0.000012
+vn -0.000002 0.519613 -0.854402
+vn -0.000002 0.519613 -0.854402
+vn -0.000002 0.519613 -0.854402
+vn -0.000002 0.519613 -0.854402
+vn 0.000000 1.000000 -0.000006
+vn 0.000000 1.000000 -0.000006
+vn 0.000000 1.000000 -0.000006
+vn 0.000000 1.000000 -0.000006
+vn -1.000000 0.000000 0.000001
+vn -1.000000 0.000000 0.000001
+vn -1.000000 0.000000 0.000001
+vn -1.000000 0.000000 0.000001
+vn 0.000006 1.000000 -0.000000
+vn 0.000006 1.000000 -0.000000
+vn 0.000006 1.000000 -0.000000
+vn 0.000006 1.000000 -0.000000
+vn 0.819683 0.572818 -0.000001
+vn 0.819683 0.572818 -0.000001
+vn 0.819683 0.572818 -0.000001
+vn 0.819683 0.572818 -0.000001
+vn 0.719141 0.694865 -0.000000
+vn 0.719141 0.694864 -0.000000
+vn 0.367513 0.930019 -0.000000
+vn 0.367513 0.930019 -0.000000
+vn 0.078424 0.996920 0.000000
+vn 0.078424 0.996920 0.000000
+vn 0.854431 0.519565 0.000000
+vn 0.854431 0.519565 0.000000
+vn 0.854431 0.519565 0.000000
+vn 0.854431 0.519565 0.000000
+vn 0.000012 1.000000 0.000000
+vn 0.000012 1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.707093 -0.000030 -0.707121
+vn 0.707093 -0.000030 -0.707121
+vn 0.707093 -0.000030 -0.707121
+vn 0.707110 -0.000009 -0.707104
+vn 0.707110 -0.000009 -0.707104
+vn 0.707110 -0.000009 -0.707104
+vn 0.707110 -0.000009 -0.707104
+vn 0.707100 0.000014 -0.707113
+vn 0.707100 0.000014 -0.707113
+vn 0.707100 0.000014 -0.707113
+vn 0.707130 0.000000 -0.707083
+vn 0.707131 0.000000 -0.707083
+vn 0.707130 0.000000 -0.707083
+vn 0.707103 0.000007 -0.707111
+vn 0.707103 0.000007 -0.707111
+vn 0.707103 0.000007 -0.707111
+vn 0.707103 0.000007 -0.707111
+vn 0.707110 0.000004 -0.707103
+vn 0.707110 0.000004 -0.707103
+vn 0.707110 0.000004 -0.707104
+vn 0.707113 0.000004 -0.707101
+vn 0.707113 0.000004 -0.707101
+vn 0.707113 0.000004 -0.707101
+vn 0.707109 0.000004 -0.707105
+vn 0.707109 0.000004 -0.707105
+vn 0.707109 0.000004 -0.707105
+vn 0.707110 0.000004 -0.707103
+vn 0.707110 0.000004 -0.707103
+vn 0.707110 0.000004 -0.707103
+vn 0.707106 0.000003 -0.707108
+vn 0.707106 0.000003 -0.707108
+vn 0.707106 0.000003 -0.707108
+vn 0.707102 0.000002 -0.707111
+vn 0.707102 0.000002 -0.707111
+vn 0.707102 0.000002 -0.707111
+vn 0.707102 0.000002 -0.707111
+vn 0.707107 -0.000000 -0.707106
+vn 0.707107 -0.000000 -0.707106
+vn 0.707107 -0.000000 -0.707106
+vn 0.707107 0.000000 -0.707107
+vn 0.707107 0.000000 -0.707107
+vn 0.707107 0.000000 -0.707107
+vn 0.707126 0.000000 -0.707087
+vn 0.707126 0.000000 -0.707087
+vn 0.707126 0.000000 -0.707087
+vn 0.707115 0.000000 -0.707098
+vn 0.707115 0.000000 -0.707098
+vn 0.707115 0.000000 -0.707098
+vn 0.707092 -0.000015 -0.707121
+vn 0.707092 -0.000015 -0.707121
+vn 0.707092 -0.000015 -0.707121
+vn 0.707111 0.000002 -0.707102
+vn 0.707111 0.000002 -0.707102
+vn 0.707111 0.000002 -0.707103
+vn 0.707111 0.000002 -0.707103
+vn 0.707115 -0.000002 -0.707099
+vn 0.707114 -0.000002 -0.707099
+vn 0.707114 -0.000002 -0.707099
+vn 0.707117 -0.000003 -0.707097
+vn 0.707117 -0.000003 -0.707097
+vn 0.707117 -0.000003 -0.707097
+vn 0.707126 -0.000003 -0.707088
+vn 0.707126 -0.000003 -0.707088
+vn 0.707126 -0.000003 -0.707088
+vn 0.707112 -0.000007 -0.707102
+vn 0.707112 -0.000007 -0.707102
+vn 0.707112 -0.000007 -0.707102
+vn 0.707090 -0.000022 -0.707123
+vn 0.707090 -0.000022 -0.707124
+vn 0.707090 -0.000022 -0.707123
+vn 0.707112 0.000005 -0.707101
+vn 0.707112 0.000005 -0.707101
+vn 0.707112 0.000005 -0.707101
+vn 0.707112 0.000005 -0.707101
+vn 0.707126 0.000000 -0.707088
+vn 0.707126 0.000000 -0.707087
+vn 0.707126 0.000000 -0.707088
+vn 0.707107 -0.000014 -0.707107
+vn 0.707107 -0.000014 -0.707107
+vn 0.707107 -0.000014 -0.707107
+vn 0.707109 -0.000020 -0.707105
+vn 0.707109 -0.000020 -0.707105
+vn 0.707109 -0.000020 -0.707105
+vn 0.707109 -0.000020 -0.707105
+vn 0.707130 -0.000075 -0.707084
+vn 0.707130 -0.000075 -0.707083
+vn 0.707130 -0.000075 -0.707084
+vn 0.707067 0.000000 -0.707147
+vn 0.707067 0.000000 -0.707147
+vn 0.707067 0.000000 -0.707147
+s 1
+f 4/1/1 3/2/2 27/3/3 28/4/4
+f 5/5/5 4/1/1 28/4/4 29/6/6
+f 6/7/7 5/5/5 29/6/6 30/8/8
+f 6/7/7 30/8/8 31/10/9 7/9/10
+s 2
+f 16/11/11 10/12/12 20/13/13 21/14/14
+f 16/11/11 21/14/14 22/16/15 17/15/16
+f 17/15/16 22/16/15 23/17/17 12/18/18
+s 3
+f 4/19/19 41/20/20 42/21/21 3/22/22
+f 5/23/23 40/24/24 41/20/20 4/19/19
+f 6/25/25 39/26/26 40/24/24 5/23/23
+f 39/26/26 6/25/25 7/27/27 38/28/28
+s off
+f 1/29/29 50/30/30 51/31/31 2/32/32
+f 14/33/33 37/34/34 38/28/35 7/27/36
+s 3
+f 13/37/37 43/38/38 44/39/39 15/40/40
+f 11/36/41 15/40/40 44/39/39 45/35/42
+s 4
+f 16/41/43 48/42/44 49/43/45 10/44/46
+f 48/42/44 16/41/43 17/45/47 47/46/48
+f 47/46/48 17/45/47 12/47/49 46/48/50
+s off
+f 9/49/51 8/51/52 35/52/53 36/50/54
+s 3
+f 13/37/37 3/22/22 42/21/21 43/38/38
+s 5
+f 1/29/55 10/44/56 49/43/57 50/30/58
+s off
+f 12/47/59 11/36/60 45/35/61 46/48/62
+f 37/34/63 14/33/64 9/49/65 36/50/66
+f 33/53/67 34/54/68 8/55/69 9/56/70
+f 14/58/71 32/57/72 33/53/73 9/56/74
+f 7/9/75 31/10/76 32/57/77 14/58/78
+s 1
+f 13/59/79 26/60/80 27/3/3 3/2/2
+f 25/61/81 26/60/80 13/59/79 15/62/82
+f 11/63/83 24/64/84 25/61/81 15/62/82
+s off
+f 23/17/85 24/64/86 11/63/87 12/18/88
+s 5
+f 1/65/55 19/66/89 20/13/90 10/12/56
+s off
+f 18/67/91 19/66/92 1/65/93 2/68/94
+f 21/69/95 20/70/96 18/71/97
+f 18/71/98 52/73/99 22/72/100 21/69/101
+f 52/73/102 23/74/103 22/72/104
+f 24/75/105 23/74/106 52/73/107
+f 53/76/108 25/77/109 24/75/110 52/73/111
+f 26/78/112 25/77/113 53/76/114
+f 27/79/115 26/78/116 53/76/117
+f 28/80/118 27/79/119 53/76/120
+f 29/81/121 28/80/122 53/76/123
+f 30/82/124 29/81/125 53/76/126
+f 53/76/127 34/84/128 31/83/129 30/82/130
+f 32/85/131 31/83/132 34/84/133
+f 34/84/134 33/86/135 32/85/136
+f 18/71/137 20/70/138 19/87/139
+f 35/89/140 37/88/141 36/106/142
+f 38/90/143 37/88/144 35/89/145
+f 54/91/146 39/92/147 38/90/148 35/89/149
+f 40/93/150 39/92/151 54/91/152
+f 41/94/153 40/93/154 54/91/155
+f 42/95/156 41/94/157 54/91/158
+f 43/96/159 42/95/160 54/91/161
+f 44/97/162 43/96/163 54/91/164
+f 54/91/165 55/99/166 45/98/167 44/97/168
+f 46/100/169 45/98/170 55/99/171
+f 55/99/172 47/101/173 46/100/174
+f 51/102/175 48/103/176 47/101/177 55/99/178
+f 49/104/179 48/103/180 51/102/181
+f 49/104/182 51/102/183 50/105/184
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.sfa b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.sfa
new file mode 100644
index 000000000..5b5086e65
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_left.sfa
@@ -0,0 +1,50 @@
+{
+ materials: [
+ {
+ name: 'unlit_material',
+ parameters: [
+ {
+ baseColor: 'frame_base',
+ },
+ {
+ baseColorTint: [
+ 1,
+ 1,
+ 1,
+ 1,
+ ],
+ },
+ {
+ metallic: 1,
+ },
+ {
+ roughness: 1,
+ },
+ {
+ opacity: null,
+ },
+ ],
+ source: 'build/sceneform_sdk/default_materials/obj_material.sfm',
+ },
+ ],
+ model: {
+ attributes: [
+ 'Position',
+ 'TexCoord',
+ 'Orientation',
+ ],
+ collision: {},
+ file: 'sampledata/models/frame_lower_left.obj',
+ name: 'frame_lower_left',
+ recenter: 'root',
+ scale: 1.0006200000000001,
+ },
+ samplers: [
+ {
+ file: 'sampledata/models\\frame_base.png',
+ name: 'frame_base',
+ pipeline_name: 'frame_base.png',
+ },
+ ],
+ version: '0.54:2',
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.mtl b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.mtl
new file mode 100644
index 000000000..27ca7b066
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.mtl
@@ -0,0 +1,7 @@
+newmtl unlit_material
+illum 2
+Kd 0.00 0.00 0.00
+Ka 0.00 0.00 0.00
+Tf 1.00 1.00 1.00
+map_Kd frame_base.png
+Ni 1.00
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.obj b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.obj
new file mode 100644
index 000000000..4be0c4a68
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.obj
@@ -0,0 +1,420 @@
+mtllib frame_lower_right.mtl
+v -0.019500 0.001781 -0.019500
+v -0.019500 -0.000000 -0.019500
+v -0.009750 0.004591 -0.009750
+v -0.009027 0.005968 -0.009027
+v -0.007902 0.007060 -0.007902
+v -0.006484 0.007761 -0.006484
+v -0.003340 0.007761 -0.003340
+v 0.000000 0.000000 0.000000
+v 0.000000 0.009051 0.000000
+v -0.018467 0.001781 -0.018467
+v -0.014182 0.002906 -0.014182
+v -0.014636 0.002160 -0.014636
+v -0.010457 0.003749 -0.010457
+v -0.002438 0.009051 -0.002438
+v -0.011328 0.003131 -0.011328
+v -0.017860 0.002160 -0.017860
+v -0.016248 0.002488 -0.016248
+v -0.030469 -0.000000 -0.019500
+v -0.030469 0.001781 -0.019500
+v -0.031502 0.001781 -0.018467
+v -0.032109 0.002160 -0.017860
+v -0.033721 0.002488 -0.016248
+v -0.035333 0.002160 -0.014636
+v -0.035787 0.002906 -0.014182
+v -0.038641 0.003131 -0.011328
+v -0.039512 0.003749 -0.010457
+v -0.040219 0.004591 -0.009750
+v -0.040942 0.005968 -0.009027
+v -0.042067 0.007060 -0.007902
+v -0.043485 0.007761 -0.006484
+v -0.046629 0.007761 -0.003340
+v -0.047530 0.009051 -0.002438
+v -0.049969 0.009051 0.000000
+v -0.049969 0.000000 0.000000
+v 0.000000 0.000000 -0.049969
+v 0.000000 0.009051 -0.049969
+v -0.002438 0.009051 -0.047530
+v -0.003340 0.007761 -0.046629
+v -0.006484 0.007761 -0.043485
+v -0.007902 0.007060 -0.042067
+v -0.009027 0.005968 -0.040941
+v -0.009750 0.004591 -0.040219
+v -0.010457 0.003749 -0.039512
+v -0.011328 0.003131 -0.038641
+v -0.014182 0.002906 -0.035787
+v -0.014636 0.002160 -0.035333
+v -0.016248 0.002488 -0.033721
+v -0.017860 0.002160 -0.032108
+v -0.018467 0.001781 -0.031502
+v -0.019500 0.001781 -0.030469
+v -0.019500 -0.000000 -0.030469
+v -0.035787 0.000000 -0.014182
+v -0.040926 0.000000 -0.009043
+v -0.009043 0.000000 -0.040925
+v -0.014182 0.000000 -0.035787
+vt 0.824364 0.145859
+vt 0.819365 0.155382
+vt 0.608566 0.155382
+vt 0.603566 0.145859
+vt 0.832151 0.138302
+vt 0.595780 0.138302
+vt 0.841962 0.128490
+vt 0.585968 0.128490
+vt 0.863714 0.106738
+vt 0.564216 0.106738
+vt 0.763252 0.212705
+vt 0.759054 0.215329
+vt 0.668875 0.215329
+vt 0.664677 0.212705
+vt 0.774407 0.201550
+vt 0.653523 0.201550
+vt 0.642368 0.190396
+vt 0.785562 0.190396
+vt 0.851849 0.177930
+vt 0.851848 0.398727
+vt 0.842325 0.393727
+vt 0.842326 0.182929
+vt 0.859406 0.170143
+vt 0.859406 0.406513
+vt 0.869218 0.160332
+vt 0.869217 0.416325
+vt 0.890970 0.138579
+vt 0.890970 0.438077
+vt 0.775234 0.250384
+vt 0.775234 0.326272
+vt 0.762912 0.326272
+vt 0.762912 0.250384
+vt 0.899893 0.132344
+vt 0.899893 0.444312
+vt 0.812473 0.363063
+vt 0.812473 0.213593
+vt 0.836497 0.187818
+vt 0.836496 0.388838
+vt 0.832222 0.382812
+vt 0.832222 0.193844
+vt 0.785003 0.239040
+vt 0.785003 0.337616
+vt 0.782379 0.333417
+vt 0.782379 0.243239
+vt 0.796158 0.227886
+vt 0.796157 0.348770
+vt 0.807312 0.216732
+vt 0.807312 0.359924
+vt 0.916763 0.115474
+vt 0.916763 0.461182
+vt 0.979383 0.115474
+vt 0.979383 0.461182
+vt 0.541111 0.080945
+vt 0.541111 0.018325
+vt 0.886820 0.018325
+vt 0.886820 0.080946
+vt 0.557981 0.097815
+vt 0.869950 0.097815
+vt 0.814476 0.161211
+vt 0.613455 0.161211
+vt 0.619481 0.165485
+vt 0.808449 0.165486
+vt 0.788700 0.185235
+vt 0.639230 0.185235
+vt 0.751909 0.222474
+vt 0.676020 0.222473
+vt 0.676020 0.234795
+vt 0.751909 0.234796
+vt 0.662955 0.247360
+vt 0.667791 0.250383
+vt 0.676020 0.264576
+vt 0.650105 0.244751
+vt 0.633641 0.264576
+vt 0.637257 0.247360
+vt 0.633641 0.241415
+vt 0.592687 0.264576
+vt 0.610893 0.239625
+vt 0.603951 0.234701
+vt 0.598319 0.227987
+vt 0.592560 0.217017
+vt 0.583591 0.208312
+vt 0.572289 0.202723
+vt 0.547232 0.202722
+vt 0.520617 0.264577
+vt 0.540049 0.192444
+vt 0.520617 0.192444
+vt 0.676020 0.250383
+vt 0.801595 0.462243
+vt 0.729463 0.481675
+vt 0.791317 0.455060
+vt 0.729463 0.409606
+vt 0.791317 0.430004
+vt 0.785728 0.418702
+vt 0.777022 0.409733
+vt 0.766053 0.403974
+vt 0.759338 0.398342
+vt 0.754414 0.391400
+vt 0.752625 0.368652
+vt 0.729463 0.368652
+vt 0.746679 0.365036
+vt 0.749289 0.352188
+vt 0.729463 0.326272
+vt 0.746679 0.339339
+vt 0.743657 0.334503
+vt 0.743657 0.326272
+vt 0.801595 0.481675
+vn 0.000000 0.642146 -0.766582
+vn 0.000000 0.511782 -0.859116
+vn 0.000000 0.511782 -0.859116
+vn 0.000000 0.642146 -0.766582
+vn -0.000000 0.855441 -0.517901
+vn -0.000000 0.855441 -0.517901
+vn -0.000000 0.998103 -0.061574
+vn -0.000000 0.998103 -0.061575
+vn -0.000000 1.000000 -0.000000
+vn -0.000000 1.000000 -0.000000
+vn 0.000001 0.970554 -0.240884
+vn 0.000002 0.847989 -0.530014
+vn 0.000002 0.847989 -0.530014
+vn 0.000001 0.970554 -0.240884
+vn -0.000000 0.993588 0.113066
+vn -0.000000 0.993587 0.113067
+vn -0.000000 0.979994 0.199028
+vn -0.000000 0.979994 0.199028
+vn -0.766576 0.642154 0.000001
+vn -0.766577 0.642153 0.000001
+vn -0.859115 0.511782 0.000002
+vn -0.859116 0.511782 0.000002
+vn -0.517894 0.855445 0.000000
+vn -0.517893 0.855445 0.000000
+vn -0.061577 0.998102 -0.000000
+vn -0.061576 0.998102 -0.000000
+vn -0.000000 1.000000 -0.000000
+vn -0.000000 1.000000 -0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.819700 0.572793 -0.000000
+vn -0.819700 0.572793 -0.000000
+vn -0.819700 0.572793 -0.000000
+vn -0.819700 0.572793 -0.000000
+vn -0.719145 0.694860 0.000001
+vn -0.719146 0.694859 0.000001
+vn -0.367521 0.930015 0.000000
+vn -0.367523 0.930014 0.000000
+vn -0.078425 0.996920 0.000000
+vn -0.078425 0.996920 0.000000
+vn -0.240883 0.970554 0.000000
+vn -0.240885 0.970554 0.000000
+vn -0.529995 0.848001 0.000001
+vn -0.529995 0.848001 0.000001
+vn 0.113066 0.993588 -0.000000
+vn 0.113066 0.993587 -0.000000
+vn 0.199028 0.979994 -0.000000
+vn 0.199028 0.979994 -0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn -0.000006 1.000000 -0.000006
+vn -0.000006 1.000000 -0.000006
+vn -0.000012 1.000000 0.000000
+vn -0.000012 1.000000 0.000000
+vn -0.854400 0.519615 0.000002
+vn -0.854400 0.519616 0.000002
+vn -0.854400 0.519616 0.000002
+vn -0.854400 0.519615 0.000002
+vn -0.000006 1.000000 -0.000000
+vn -0.000006 1.000000 -0.000000
+vn -0.000006 1.000000 -0.000000
+vn -0.000006 1.000000 -0.000000
+vn 0.000001 0.000000 1.000000
+vn 0.000001 0.000000 1.000000
+vn 0.000001 0.000000 1.000000
+vn 0.000001 0.000000 1.000000
+vn -0.000000 1.000000 -0.000006
+vn -0.000000 1.000000 -0.000006
+vn -0.000000 1.000000 -0.000006
+vn -0.000000 1.000000 -0.000006
+vn -0.000001 0.572818 -0.819683
+vn -0.000001 0.572818 -0.819683
+vn -0.000001 0.572818 -0.819683
+vn -0.000001 0.572818 -0.819683
+vn -0.000000 0.694864 -0.719141
+vn -0.000000 0.694864 -0.719141
+vn -0.000000 0.930019 -0.367513
+vn -0.000000 0.930019 -0.367513
+vn 0.000000 0.996920 -0.078424
+vn 0.000000 0.996920 -0.078424
+vn 0.000000 0.519565 -0.854431
+vn 0.000000 0.519565 -0.854431
+vn 0.000000 0.519565 -0.854431
+vn 0.000000 0.519565 -0.854431
+vn 0.000000 1.000000 -0.000012
+vn 0.000000 1.000000 -0.000012
+vn -0.000000 0.000000 -1.000000
+vn -0.000000 0.000000 -1.000000
+vn -0.000000 0.000000 -1.000000
+vn -0.000000 0.000000 -1.000000
+vn -0.707119 -0.000022 -0.707095
+vn -0.707119 -0.000022 -0.707095
+vn -0.707119 -0.000022 -0.707095
+vn -0.707104 -0.000009 -0.707110
+vn -0.707104 -0.000009 -0.707110
+vn -0.707104 -0.000009 -0.707110
+vn -0.707104 -0.000009 -0.707110
+vn -0.707115 0.000015 -0.707099
+vn -0.707115 0.000015 -0.707099
+vn -0.707115 0.000015 -0.707099
+vn -0.707081 0.000000 -0.707132
+vn -0.707081 0.000000 -0.707132
+vn -0.707081 0.000000 -0.707132
+vn -0.707111 0.000006 -0.707103
+vn -0.707111 0.000006 -0.707103
+vn -0.707111 0.000006 -0.707103
+vn -0.707111 0.000006 -0.707103
+vn -0.707105 0.000005 -0.707109
+vn -0.707105 0.000005 -0.707109
+vn -0.707105 0.000005 -0.707109
+vn -0.707105 0.000003 -0.707109
+vn -0.707105 0.000003 -0.707109
+vn -0.707105 0.000003 -0.707109
+vn -0.707104 0.000004 -0.707110
+vn -0.707104 0.000004 -0.707110
+vn -0.707104 0.000004 -0.707110
+vn -0.707104 0.000004 -0.707109
+vn -0.707104 0.000004 -0.707109
+vn -0.707104 0.000004 -0.707109
+vn -0.707106 0.000003 -0.707108
+vn -0.707106 0.000003 -0.707108
+vn -0.707106 0.000003 -0.707108
+vn -0.707111 0.000002 -0.707102
+vn -0.707111 0.000002 -0.707103
+vn -0.707111 0.000002 -0.707103
+vn -0.707111 0.000002 -0.707102
+vn -0.707106 0.000001 -0.707107
+vn -0.707106 0.000001 -0.707107
+vn -0.707106 0.000001 -0.707107
+vn -0.707107 0.000000 -0.707107
+vn -0.707107 0.000000 -0.707107
+vn -0.707107 0.000000 -0.707107
+vn -0.707087 0.000000 -0.707127
+vn -0.707087 0.000000 -0.707127
+vn -0.707087 0.000000 -0.707127
+vn -0.707098 0.000000 -0.707116
+vn -0.707098 0.000000 -0.707116
+vn -0.707098 0.000000 -0.707116
+vn -0.707123 -0.000013 -0.707090
+vn -0.707123 -0.000013 -0.707090
+vn -0.707123 -0.000013 -0.707090
+vn -0.707102 0.000002 -0.707111
+vn -0.707102 0.000002 -0.707111
+vn -0.707102 0.000002 -0.707111
+vn -0.707102 0.000002 -0.707111
+vn -0.707101 -0.000002 -0.707113
+vn -0.707101 -0.000002 -0.707113
+vn -0.707101 -0.000002 -0.707113
+vn -0.707098 -0.000003 -0.707116
+vn -0.707098 -0.000003 -0.707116
+vn -0.707098 -0.000003 -0.707116
+vn -0.707087 -0.000003 -0.707127
+vn -0.707087 -0.000003 -0.707127
+vn -0.707087 -0.000003 -0.707127
+vn -0.707102 -0.000007 -0.707111
+vn -0.707102 -0.000007 -0.707111
+vn -0.707102 -0.000007 -0.707111
+vn -0.707122 -0.000024 -0.707092
+vn -0.707122 -0.000024 -0.707092
+vn -0.707121 -0.000024 -0.707092
+vn -0.707102 0.000004 -0.707112
+vn -0.707102 0.000004 -0.707112
+vn -0.707102 0.000004 -0.707112
+vn -0.707102 0.000004 -0.707112
+vn -0.707086 0.000000 -0.707127
+vn -0.707086 0.000000 -0.707127
+vn -0.707086 0.000000 -0.707127
+vn -0.707107 -0.000008 -0.707106
+vn -0.707107 -0.000008 -0.707106
+vn -0.707107 -0.000008 -0.707106
+vn -0.707108 -0.000013 -0.707106
+vn -0.707108 -0.000013 -0.707106
+vn -0.707108 -0.000013 -0.707106
+vn -0.707108 -0.000013 -0.707106
+vn -0.707023 -0.000131 -0.707190
+vn -0.707023 -0.000131 -0.707190
+vn -0.707023 -0.000131 -0.707190
+vn -0.707147 0.000000 -0.707067
+vn -0.707147 0.000000 -0.707067
+vn -0.707147 0.000000 -0.707067
+s 1
+f 4/1/1 3/2/2 27/3/3 28/4/4
+f 5/5/5 4/1/1 28/4/4 29/6/6
+f 6/7/7 5/5/5 29/6/6 30/8/8
+f 6/7/7 30/8/8 31/10/9 7/9/10
+s 2
+f 16/11/11 10/12/12 20/13/13 21/14/14
+f 16/11/11 21/14/14 22/16/15 17/15/16
+f 17/15/16 22/16/15 23/17/17 12/18/18
+s 3
+f 4/19/19 41/20/20 42/21/21 3/22/22
+f 5/23/23 40/24/24 41/20/20 4/19/19
+f 6/25/25 39/26/26 40/24/24 5/23/23
+f 39/26/26 6/25/25 7/27/27 38/28/28
+s off
+f 1/29/29 50/30/30 51/31/31 2/32/32
+f 14/33/33 37/34/34 38/28/35 7/27/36
+s 3
+f 13/37/37 43/38/38 44/39/39 15/40/40
+f 11/36/41 15/40/40 44/39/39 45/35/42
+s 4
+f 16/41/43 48/42/44 49/43/45 10/44/46
+f 48/42/44 16/41/43 17/45/47 47/46/48
+f 47/46/48 17/45/47 12/47/49 46/48/50
+s off
+f 9/49/51 8/51/52 35/52/53 36/50/54
+s 3
+f 13/37/37 3/22/22 42/21/21 43/38/38
+s 5
+f 1/29/55 10/44/56 49/43/57 50/30/58
+s off
+f 12/47/59 11/36/60 45/35/61 46/48/62
+f 37/34/63 14/33/64 9/49/65 36/50/66
+f 33/53/67 34/54/68 8/55/69 9/56/70
+f 14/58/71 32/57/72 33/53/73 9/56/74
+f 7/9/75 31/10/76 32/57/77 14/58/78
+s 1
+f 13/59/79 26/60/80 27/3/3 3/2/2
+f 25/61/81 26/60/80 13/59/79 15/62/82
+f 11/63/83 24/64/84 25/61/81 15/62/82
+s off
+f 23/17/85 24/64/86 11/63/87 12/18/88
+s 5
+f 1/65/55 19/66/89 20/13/90 10/12/56
+s off
+f 18/67/91 19/66/92 1/65/93 2/68/94
+f 21/69/95 20/70/96 18/71/97
+f 18/71/98 52/73/99 22/72/100 21/69/101
+f 52/73/102 23/74/103 22/72/104
+f 24/75/105 23/74/106 52/73/107
+f 53/76/108 25/77/109 24/75/110 52/73/111
+f 26/78/112 25/77/113 53/76/114
+f 27/79/115 26/78/116 53/76/117
+f 28/80/118 27/79/119 53/76/120
+f 29/81/121 28/80/122 53/76/123
+f 30/82/124 29/81/125 53/76/126
+f 53/76/127 34/84/128 31/83/129 30/82/130
+f 32/85/131 31/83/132 34/84/133
+f 34/84/134 33/86/135 32/85/136
+f 18/71/137 20/70/138 19/87/139
+f 35/89/140 37/88/141 36/106/142
+f 38/90/143 37/88/144 35/89/145
+f 54/91/146 39/92/147 38/90/148 35/89/149
+f 40/93/150 39/92/151 54/91/152
+f 41/94/153 40/93/154 54/91/155
+f 42/95/156 41/94/157 54/91/158
+f 43/96/159 42/95/160 54/91/161
+f 44/97/162 43/96/163 54/91/164
+f 54/91/165 55/99/166 45/98/167 44/97/168
+f 46/100/169 45/98/170 55/99/171
+f 55/99/172 47/101/173 46/100/174
+f 51/102/175 48/103/176 47/101/177 55/99/178
+f 49/104/179 48/103/180 51/102/181
+f 49/104/182 51/102/183 50/105/184
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.sfa b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.sfa
new file mode 100644
index 000000000..3a62bedd1
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_lower_right.sfa
@@ -0,0 +1,50 @@
+{
+ materials: [
+ {
+ name: 'unlit_material',
+ parameters: [
+ {
+ baseColor: 'frame_base',
+ },
+ {
+ baseColorTint: [
+ 1,
+ 1,
+ 1,
+ 1,
+ ],
+ },
+ {
+ metallic: 1,
+ },
+ {
+ roughness: 1,
+ },
+ {
+ opacity: null,
+ },
+ ],
+ source: 'build/sceneform_sdk/default_materials/obj_material.sfm',
+ },
+ ],
+ model: {
+ attributes: [
+ 'Position',
+ 'TexCoord',
+ 'Orientation',
+ ],
+ collision: {},
+ file: 'sampledata/models/frame_lower_right.obj',
+ name: 'frame_lower_right',
+ recenter: 'root',
+ scale: 1.0006200000000001,
+ },
+ samplers: [
+ {
+ file: 'sampledata/models\\frame_base.png',
+ name: 'frame_base',
+ pipeline_name: 'frame_base.png',
+ },
+ ],
+ version: '0.54:2',
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.mtl b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.mtl
new file mode 100644
index 000000000..27ca7b066
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.mtl
@@ -0,0 +1,7 @@
+newmtl unlit_material
+illum 2
+Kd 0.00 0.00 0.00
+Ka 0.00 0.00 0.00
+Tf 1.00 1.00 1.00
+map_Kd frame_base.png
+Ni 1.00
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.obj b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.obj
new file mode 100644
index 000000000..5303c9e13
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.obj
@@ -0,0 +1,420 @@
+mtllib frame_upper_left.mtl
+v 0.019500 0.001781 0.019500
+v 0.019500 -0.000000 0.019500
+v 0.009750 0.004591 0.009750
+v 0.009027 0.005968 0.009027
+v 0.007902 0.007060 0.007902
+v 0.006484 0.007761 0.006484
+v 0.003340 0.007761 0.003340
+v 0.000000 0.000000 0.000000
+v 0.000000 0.009051 0.000000
+v 0.018467 0.001781 0.018467
+v 0.014182 0.002906 0.014182
+v 0.014636 0.002160 0.014636
+v 0.010457 0.003749 0.010457
+v 0.002438 0.009051 0.002438
+v 0.011328 0.003131 0.011328
+v 0.017860 0.002160 0.017860
+v 0.016248 0.002488 0.016248
+v 0.030469 -0.000000 0.019500
+v 0.030469 0.001781 0.019500
+v 0.031502 0.001781 0.018467
+v 0.032109 0.002160 0.017860
+v 0.033721 0.002488 0.016248
+v 0.035333 0.002160 0.014636
+v 0.035787 0.002906 0.014182
+v 0.038641 0.003131 0.011328
+v 0.039512 0.003749 0.010457
+v 0.040219 0.004591 0.009750
+v 0.040942 0.005968 0.009027
+v 0.042067 0.007060 0.007902
+v 0.043485 0.007761 0.006484
+v 0.046629 0.007761 0.003340
+v 0.047530 0.009051 0.002438
+v 0.049969 0.009051 -0.000000
+v 0.049969 0.000000 -0.000000
+v 0.000000 0.000000 0.049969
+v 0.000000 0.009051 0.049969
+v 0.002438 0.009051 0.047530
+v 0.003340 0.007761 0.046629
+v 0.006484 0.007761 0.043485
+v 0.007902 0.007060 0.042067
+v 0.009027 0.005968 0.040941
+v 0.009750 0.004591 0.040219
+v 0.010457 0.003749 0.039512
+v 0.011328 0.003131 0.038641
+v 0.014182 0.002906 0.035787
+v 0.014636 0.002160 0.035333
+v 0.016248 0.002488 0.033721
+v 0.017860 0.002160 0.032108
+v 0.018467 0.001781 0.031502
+v 0.019500 0.001781 0.030469
+v 0.019500 -0.000000 0.030469
+v 0.035787 0.000000 0.014182
+v 0.040926 0.000000 0.009043
+v 0.009043 0.000000 0.040925
+v 0.014182 0.000000 0.035787
+vt 0.175636 0.854141
+vt 0.180635 0.844618
+vt 0.391434 0.844618
+vt 0.396434 0.854141
+vt 0.167849 0.861698
+vt 0.404220 0.861698
+vt 0.158038 0.871510
+vt 0.414032 0.871510
+vt 0.136286 0.893262
+vt 0.435784 0.893262
+vt 0.236747 0.787295
+vt 0.240946 0.784671
+vt 0.331125 0.784671
+vt 0.335323 0.787295
+vt 0.225593 0.798450
+vt 0.346477 0.798450
+vt 0.357632 0.809604
+vt 0.214438 0.809604
+vt 0.148151 0.822070
+vt 0.148152 0.601273
+vt 0.157675 0.606273
+vt 0.157674 0.817071
+vt 0.140594 0.829857
+vt 0.140594 0.593487
+vt 0.130782 0.839668
+vt 0.130783 0.583675
+vt 0.109030 0.861421
+vt 0.109030 0.561923
+vt 0.224766 0.749616
+vt 0.224766 0.673728
+vt 0.237088 0.673728
+vt 0.237088 0.749616
+vt 0.100107 0.867656
+vt 0.100107 0.555688
+vt 0.187527 0.636937
+vt 0.187527 0.786407
+vt 0.163503 0.812182
+vt 0.163504 0.611162
+vt 0.167778 0.617188
+vt 0.167778 0.806156
+vt 0.214997 0.760960
+vt 0.214997 0.662384
+vt 0.217621 0.666583
+vt 0.217621 0.756761
+vt 0.203842 0.772114
+vt 0.203843 0.651230
+vt 0.192688 0.783268
+vt 0.192688 0.640076
+vt 0.083237 0.884526
+vt 0.083237 0.538818
+vt 0.020617 0.884526
+vt 0.020617 0.538818
+vt 0.458889 0.919055
+vt 0.458889 0.981675
+vt 0.113180 0.981675
+vt 0.113180 0.919054
+vt 0.442019 0.902185
+vt 0.130050 0.902185
+vt 0.185524 0.838789
+vt 0.386545 0.838789
+vt 0.380519 0.834515
+vt 0.191551 0.834514
+vt 0.211300 0.814765
+vt 0.360770 0.814765
+vt 0.248091 0.777526
+vt 0.323980 0.777527
+vt 0.323980 0.765205
+vt 0.248091 0.765204
+vt 0.337045 0.752640
+vt 0.332209 0.749617
+vt 0.323980 0.735424
+vt 0.349895 0.755249
+vt 0.366359 0.735424
+vt 0.362743 0.752640
+vt 0.366359 0.758585
+vt 0.407313 0.735424
+vt 0.389107 0.760375
+vt 0.396049 0.765299
+vt 0.401681 0.772013
+vt 0.407440 0.782983
+vt 0.416409 0.791688
+vt 0.427711 0.797277
+vt 0.452768 0.797278
+vt 0.479383 0.735423
+vt 0.459951 0.807556
+vt 0.479383 0.807556
+vt 0.323980 0.749617
+vt 0.198405 0.537757
+vt 0.270537 0.518325
+vt 0.208683 0.544940
+vt 0.270537 0.590394
+vt 0.208683 0.569996
+vt 0.214272 0.581298
+vt 0.222978 0.590267
+vt 0.233947 0.596026
+vt 0.240662 0.601658
+vt 0.245586 0.608600
+vt 0.247375 0.631348
+vt 0.270537 0.631348
+vt 0.253321 0.634964
+vt 0.250711 0.647812
+vt 0.270537 0.673728
+vt 0.253321 0.660661
+vt 0.256343 0.665497
+vt 0.256343 0.673728
+vt 0.198405 0.518325
+vn 0.000000 0.642146 0.766582
+vn 0.000000 0.511782 0.859116
+vn 0.000000 0.511782 0.859116
+vn 0.000000 0.642146 0.766582
+vn 0.000000 0.855441 0.517901
+vn 0.000000 0.855441 0.517901
+vn 0.000000 0.998103 0.061574
+vn 0.000000 0.998103 0.061575
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn -0.000001 0.970554 0.240884
+vn -0.000002 0.847989 0.530014
+vn -0.000002 0.847989 0.530014
+vn -0.000001 0.970554 0.240884
+vn 0.000000 0.993588 -0.113066
+vn 0.000000 0.993587 -0.113067
+vn 0.000000 0.979994 -0.199028
+vn 0.000000 0.979994 -0.199028
+vn 0.766576 0.642154 -0.000001
+vn 0.766577 0.642153 -0.000001
+vn 0.859115 0.511782 -0.000002
+vn 0.859116 0.511782 -0.000002
+vn 0.517894 0.855445 -0.000000
+vn 0.517893 0.855445 -0.000000
+vn 0.061577 0.998102 0.000000
+vn 0.061576 0.998102 0.000000
+vn 0.000000 1.000000 0.000000
+vn 0.000000 1.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 1.000000 0.000000 0.000000
+vn 0.819700 0.572793 0.000000
+vn 0.819700 0.572793 0.000000
+vn 0.819700 0.572793 0.000000
+vn 0.819700 0.572793 0.000000
+vn 0.719145 0.694860 -0.000001
+vn 0.719146 0.694859 -0.000001
+vn 0.367521 0.930015 -0.000000
+vn 0.367523 0.930014 -0.000000
+vn 0.078425 0.996920 -0.000000
+vn 0.078425 0.996920 -0.000000
+vn 0.240883 0.970554 -0.000000
+vn 0.240885 0.970554 -0.000000
+vn 0.529995 0.848001 -0.000001
+vn 0.529995 0.848001 -0.000001
+vn -0.113066 0.993588 0.000000
+vn -0.113066 0.993587 0.000000
+vn -0.199028 0.979994 0.000000
+vn -0.199028 0.979994 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn 0.000006 1.000000 0.000006
+vn 0.000006 1.000000 0.000006
+vn 0.000012 1.000000 -0.000000
+vn 0.000012 1.000000 -0.000000
+vn 0.854400 0.519615 -0.000002
+vn 0.854400 0.519616 -0.000002
+vn 0.854400 0.519616 -0.000002
+vn 0.854400 0.519615 -0.000002
+vn 0.000006 1.000000 0.000000
+vn 0.000006 1.000000 0.000000
+vn 0.000006 1.000000 0.000000
+vn 0.000006 1.000000 0.000000
+vn -0.000001 0.000000 -1.000000
+vn -0.000001 0.000000 -1.000000
+vn -0.000001 0.000000 -1.000000
+vn -0.000001 0.000000 -1.000000
+vn 0.000000 1.000000 0.000006
+vn 0.000000 1.000000 0.000006
+vn 0.000000 1.000000 0.000006
+vn 0.000000 1.000000 0.000006
+vn 0.000001 0.572818 0.819683
+vn 0.000001 0.572818 0.819683
+vn 0.000001 0.572818 0.819683
+vn 0.000001 0.572818 0.819683
+vn 0.000000 0.694864 0.719141
+vn 0.000000 0.694864 0.719141
+vn 0.000000 0.930019 0.367513
+vn 0.000000 0.930019 0.367513
+vn 0.000000 0.996920 0.078424
+vn 0.000000 0.996920 0.078424
+vn 0.000000 0.519565 0.854431
+vn 0.000000 0.519565 0.854431
+vn 0.000000 0.519565 0.854431
+vn 0.000000 0.519565 0.854431
+vn -0.000000 1.000000 0.000012
+vn -0.000000 1.000000 0.000012
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 1.000000
+vn 0.707119 -0.000022 0.707095
+vn 0.707119 -0.000022 0.707095
+vn 0.707119 -0.000022 0.707095
+vn 0.707104 -0.000009 0.707110
+vn 0.707104 -0.000009 0.707110
+vn 0.707104 -0.000009 0.707110
+vn 0.707104 -0.000009 0.707110
+vn 0.707115 0.000015 0.707099
+vn 0.707115 0.000015 0.707099
+vn 0.707115 0.000015 0.707099
+vn 0.707081 0.000000 0.707132
+vn 0.707081 0.000000 0.707132
+vn 0.707081 0.000000 0.707132
+vn 0.707111 0.000006 0.707103
+vn 0.707111 0.000006 0.707103
+vn 0.707111 0.000006 0.707103
+vn 0.707111 0.000006 0.707103
+vn 0.707105 0.000005 0.707109
+vn 0.707105 0.000005 0.707109
+vn 0.707105 0.000005 0.707109
+vn 0.707105 0.000003 0.707109
+vn 0.707105 0.000003 0.707109
+vn 0.707105 0.000003 0.707109
+vn 0.707104 0.000004 0.707110
+vn 0.707104 0.000004 0.707110
+vn 0.707104 0.000004 0.707110
+vn 0.707104 0.000004 0.707109
+vn 0.707104 0.000004 0.707109
+vn 0.707104 0.000004 0.707109
+vn 0.707106 0.000003 0.707108
+vn 0.707106 0.000003 0.707108
+vn 0.707106 0.000003 0.707108
+vn 0.707111 0.000002 0.707102
+vn 0.707111 0.000002 0.707103
+vn 0.707111 0.000002 0.707103
+vn 0.707111 0.000002 0.707102
+vn 0.707106 0.000001 0.707107
+vn 0.707106 0.000001 0.707107
+vn 0.707106 0.000001 0.707107
+vn 0.707107 0.000000 0.707107
+vn 0.707107 0.000000 0.707107
+vn 0.707107 0.000000 0.707107
+vn 0.707087 0.000000 0.707127
+vn 0.707087 0.000000 0.707127
+vn 0.707087 0.000000 0.707127
+vn 0.707098 0.000000 0.707116
+vn 0.707098 0.000000 0.707116
+vn 0.707098 0.000000 0.707116
+vn 0.707123 -0.000013 0.707090
+vn 0.707123 -0.000013 0.707090
+vn 0.707123 -0.000013 0.707090
+vn 0.707102 0.000002 0.707111
+vn 0.707102 0.000002 0.707111
+vn 0.707102 0.000002 0.707111
+vn 0.707102 0.000002 0.707111
+vn 0.707101 -0.000002 0.707113
+vn 0.707101 -0.000002 0.707113
+vn 0.707101 -0.000002 0.707113
+vn 0.707098 -0.000003 0.707116
+vn 0.707098 -0.000003 0.707116
+vn 0.707098 -0.000003 0.707116
+vn 0.707087 -0.000003 0.707127
+vn 0.707087 -0.000003 0.707127
+vn 0.707087 -0.000003 0.707127
+vn 0.707102 -0.000007 0.707111
+vn 0.707102 -0.000007 0.707111
+vn 0.707102 -0.000007 0.707111
+vn 0.707122 -0.000024 0.707092
+vn 0.707122 -0.000024 0.707092
+vn 0.707121 -0.000024 0.707092
+vn 0.707102 0.000004 0.707112
+vn 0.707102 0.000004 0.707112
+vn 0.707102 0.000004 0.707112
+vn 0.707102 0.000004 0.707112
+vn 0.707086 0.000000 0.707127
+vn 0.707086 0.000000 0.707127
+vn 0.707086 0.000000 0.707127
+vn 0.707107 -0.000008 0.707106
+vn 0.707107 -0.000008 0.707106
+vn 0.707107 -0.000008 0.707106
+vn 0.707108 -0.000013 0.707106
+vn 0.707108 -0.000013 0.707106
+vn 0.707108 -0.000013 0.707106
+vn 0.707108 -0.000013 0.707106
+vn 0.707023 -0.000131 0.707190
+vn 0.707023 -0.000131 0.707190
+vn 0.707023 -0.000131 0.707190
+vn 0.707147 0.000000 0.707067
+vn 0.707147 0.000000 0.707067
+vn 0.707147 0.000000 0.707067
+s 1
+f 4/1/1 3/2/2 27/3/3 28/4/4
+f 5/5/5 4/1/1 28/4/4 29/6/6
+f 6/7/7 5/5/5 29/6/6 30/8/8
+f 6/7/7 30/8/8 31/10/9 7/9/10
+s 2
+f 16/11/11 10/12/12 20/13/13 21/14/14
+f 16/11/11 21/14/14 22/16/15 17/15/16
+f 17/15/16 22/16/15 23/17/17 12/18/18
+s 3
+f 4/19/19 41/20/20 42/21/21 3/22/22
+f 5/23/23 40/24/24 41/20/20 4/19/19
+f 6/25/25 39/26/26 40/24/24 5/23/23
+f 39/26/26 6/25/25 7/27/27 38/28/28
+s off
+f 1/29/29 50/30/30 51/31/31 2/32/32
+f 14/33/33 37/34/34 38/28/35 7/27/36
+s 3
+f 13/37/37 43/38/38 44/39/39 15/40/40
+f 11/36/41 15/40/40 44/39/39 45/35/42
+s 4
+f 16/41/43 48/42/44 49/43/45 10/44/46
+f 48/42/44 16/41/43 17/45/47 47/46/48
+f 47/46/48 17/45/47 12/47/49 46/48/50
+s off
+f 9/49/51 8/51/52 35/52/53 36/50/54
+s 3
+f 13/37/37 3/22/22 42/21/21 43/38/38
+s 5
+f 1/29/55 10/44/56 49/43/57 50/30/58
+s off
+f 12/47/59 11/36/60 45/35/61 46/48/62
+f 37/34/63 14/33/64 9/49/65 36/50/66
+f 33/53/67 34/54/68 8/55/69 9/56/70
+f 14/58/71 32/57/72 33/53/73 9/56/74
+f 7/9/75 31/10/76 32/57/77 14/58/78
+s 1
+f 13/59/79 26/60/80 27/3/3 3/2/2
+f 25/61/81 26/60/80 13/59/79 15/62/82
+f 11/63/83 24/64/84 25/61/81 15/62/82
+s off
+f 23/17/85 24/64/86 11/63/87 12/18/88
+s 5
+f 1/65/55 19/66/89 20/13/90 10/12/56
+s off
+f 18/67/91 19/66/92 1/65/93 2/68/94
+f 21/69/95 20/70/96 18/71/97
+f 18/71/98 52/73/99 22/72/100 21/69/101
+f 52/73/102 23/74/103 22/72/104
+f 24/75/105 23/74/106 52/73/107
+f 53/76/108 25/77/109 24/75/110 52/73/111
+f 26/78/112 25/77/113 53/76/114
+f 27/79/115 26/78/116 53/76/117
+f 28/80/118 27/79/119 53/76/120
+f 29/81/121 28/80/122 53/76/123
+f 30/82/124 29/81/125 53/76/126
+f 53/76/127 34/84/128 31/83/129 30/82/130
+f 32/85/131 31/83/132 34/84/133
+f 34/84/134 33/86/135 32/85/136
+f 18/71/137 20/70/138 19/87/139
+f 35/89/140 37/88/141 36/106/142
+f 38/90/143 37/88/144 35/89/145
+f 54/91/146 39/92/147 38/90/148 35/89/149
+f 40/93/150 39/92/151 54/91/152
+f 41/94/153 40/93/154 54/91/155
+f 42/95/156 41/94/157 54/91/158
+f 43/96/159 42/95/160 54/91/161
+f 44/97/162 43/96/163 54/91/164
+f 54/91/165 55/99/166 45/98/167 44/97/168
+f 46/100/169 45/98/170 55/99/171
+f 55/99/172 47/101/173 46/100/174
+f 51/102/175 48/103/176 47/101/177 55/99/178
+f 49/104/179 48/103/180 51/102/181
+f 49/104/182 51/102/183 50/105/184
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.sfa b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.sfa
new file mode 100644
index 000000000..b3d85cfc1
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_left.sfa
@@ -0,0 +1,50 @@
+{
+ materials: [
+ {
+ name: 'unlit_material',
+ parameters: [
+ {
+ baseColor: 'frame_base',
+ },
+ {
+ baseColorTint: [
+ 1,
+ 1,
+ 1,
+ 1,
+ ],
+ },
+ {
+ metallic: 1,
+ },
+ {
+ roughness: 1,
+ },
+ {
+ opacity: null,
+ },
+ ],
+ source: 'build/sceneform_sdk/default_materials/obj_material.sfm',
+ },
+ ],
+ model: {
+ attributes: [
+ 'Position',
+ 'TexCoord',
+ 'Orientation',
+ ],
+ collision: {},
+ file: 'sampledata/models/frame_upper_left.obj',
+ name: 'frame_upper_left',
+ recenter: 'root',
+ scale: 1.0006200000000001,
+ },
+ samplers: [
+ {
+ file: 'sampledata/models\\frame_base.png',
+ name: 'frame_base',
+ pipeline_name: 'frame_base.png',
+ },
+ ],
+ version: '0.54:2',
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.mtl b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.mtl
new file mode 100644
index 000000000..27ca7b066
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.mtl
@@ -0,0 +1,7 @@
+newmtl unlit_material
+illum 2
+Kd 0.00 0.00 0.00
+Ka 0.00 0.00 0.00
+Tf 1.00 1.00 1.00
+map_Kd frame_base.png
+Ni 1.00
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.obj b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.obj
new file mode 100644
index 000000000..3b4863c4c
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.obj
@@ -0,0 +1,420 @@
+mtllib frame_upper_right.mtl
+v -0.019500 0.001781 0.019500
+v -0.019500 -0.000000 0.019500
+v -0.009750 0.004591 0.009750
+v -0.009027 0.005968 0.009027
+v -0.007902 0.007060 0.007902
+v -0.006484 0.007761 0.006484
+v -0.003340 0.007761 0.003340
+v 0.000000 0.000000 0.000000
+v 0.000000 0.009051 0.000000
+v -0.018467 0.001781 0.018467
+v -0.014182 0.002906 0.014182
+v -0.014636 0.002160 0.014636
+v -0.010457 0.003749 0.010457
+v -0.002438 0.009051 0.002438
+v -0.011328 0.003131 0.011328
+v -0.017860 0.002160 0.017860
+v -0.016248 0.002488 0.016248
+v -0.019500 -0.000000 0.030469
+v -0.019500 0.001781 0.030469
+v -0.018467 0.001781 0.031502
+v -0.017860 0.002160 0.032109
+v -0.016248 0.002488 0.033721
+v -0.014636 0.002160 0.035333
+v -0.014182 0.002906 0.035787
+v -0.011328 0.003131 0.038641
+v -0.010457 0.003749 0.039512
+v -0.009750 0.004591 0.040219
+v -0.009027 0.005968 0.040942
+v -0.007902 0.007060 0.042067
+v -0.006484 0.007761 0.043485
+v -0.003340 0.007761 0.046629
+v -0.002438 0.009051 0.047530
+v 0.000000 0.009051 0.049969
+v 0.000000 0.000000 0.049969
+v -0.049969 0.000000 0.000000
+v -0.049969 0.009051 0.000000
+v -0.047530 0.009051 0.002438
+v -0.046629 0.007761 0.003340
+v -0.043485 0.007761 0.006484
+v -0.042067 0.007060 0.007902
+v -0.040941 0.005968 0.009027
+v -0.040219 0.004591 0.009750
+v -0.039512 0.003749 0.010457
+v -0.038641 0.003131 0.011328
+v -0.035787 0.002906 0.014182
+v -0.035333 0.002160 0.014636
+v -0.033721 0.002488 0.016248
+v -0.032108 0.002160 0.017860
+v -0.031502 0.001781 0.018467
+v -0.030469 0.001781 0.019500
+v -0.030469 -0.000000 0.019500
+v -0.014182 0.000000 0.035787
+v -0.009043 0.000000 0.040926
+v -0.040925 0.000000 0.009043
+v -0.035787 0.000000 0.014182
+vt 0.854141 0.824364
+vt 0.844618 0.819365
+vt 0.844618 0.608566
+vt 0.854141 0.603566
+vt 0.861698 0.832151
+vt 0.861698 0.595780
+vt 0.871510 0.841962
+vt 0.871510 0.585968
+vt 0.893262 0.863714
+vt 0.893262 0.564216
+vt 0.787295 0.763252
+vt 0.784671 0.759054
+vt 0.784671 0.668875
+vt 0.787295 0.664677
+vt 0.798450 0.774407
+vt 0.798450 0.653523
+vt 0.809604 0.642368
+vt 0.809604 0.785562
+vt 0.822070 0.851849
+vt 0.601273 0.851848
+vt 0.606273 0.842325
+vt 0.817071 0.842326
+vt 0.829857 0.859406
+vt 0.593487 0.859406
+vt 0.839668 0.869218
+vt 0.583675 0.869217
+vt 0.861421 0.890970
+vt 0.561923 0.890970
+vt 0.749616 0.775234
+vt 0.673728 0.775234
+vt 0.673728 0.762912
+vt 0.749616 0.762912
+vt 0.867656 0.899893
+vt 0.555688 0.899893
+vt 0.636937 0.812473
+vt 0.786407 0.812473
+vt 0.812182 0.836497
+vt 0.611162 0.836496
+vt 0.617188 0.832222
+vt 0.806156 0.832222
+vt 0.760960 0.785003
+vt 0.662384 0.785003
+vt 0.666583 0.782379
+vt 0.756761 0.782379
+vt 0.772114 0.796158
+vt 0.651230 0.796157
+vt 0.783268 0.807312
+vt 0.640076 0.807312
+vt 0.884526 0.916763
+vt 0.538818 0.916763
+vt 0.884526 0.979383
+vt 0.538818 0.979383
+vt 0.919055 0.541111
+vt 0.981675 0.541111
+vt 0.981675 0.886820
+vt 0.919054 0.886820
+vt 0.902185 0.557981
+vt 0.902185 0.869950
+vt 0.838789 0.814476
+vt 0.838789 0.613455
+vt 0.834515 0.619481
+vt 0.834514 0.808449
+vt 0.814765 0.788700
+vt 0.814765 0.639230
+vt 0.777526 0.751909
+vt 0.777527 0.676020
+vt 0.765205 0.676020
+vt 0.765204 0.751909
+vt 0.752640 0.662955
+vt 0.749617 0.667791
+vt 0.735424 0.676020
+vt 0.755249 0.650105
+vt 0.735424 0.633641
+vt 0.752640 0.637257
+vt 0.758585 0.633641
+vt 0.735424 0.592687
+vt 0.760375 0.610893
+vt 0.765299 0.603951
+vt 0.772013 0.598319
+vt 0.782983 0.592560
+vt 0.791688 0.583591
+vt 0.797277 0.572289
+vt 0.797278 0.547232
+vt 0.735423 0.520617
+vt 0.807556 0.540049
+vt 0.807556 0.520617
+vt 0.749617 0.676020
+vt 0.537757 0.801595
+vt 0.518325 0.729463
+vt 0.544940 0.791317
+vt 0.590394 0.729463
+vt 0.569996 0.791317
+vt 0.581298 0.785728
+vt 0.590267 0.777022
+vt 0.596026 0.766053
+vt 0.601658 0.759338
+vt 0.608600 0.754414
+vt 0.631348 0.752625
+vt 0.631348 0.729463
+vt 0.634964 0.746679
+vt 0.647812 0.749289
+vt 0.673728 0.729463
+vt 0.660661 0.746679
+vt 0.665497 0.743657
+vt 0.673728 0.743657
+vt 0.518325 0.801595
+vn -0.766579 0.642150 0.000000
+vn -0.859113 0.511787 0.000001
+vn -0.859112 0.511787 0.000001
+vn -0.766579 0.642150 0.000000
+vn -0.517895 0.855444 0.000001
+vn -0.517895 0.855444 0.000001
+vn -0.061575 0.998103 0.000000
+vn -0.061576 0.998102 0.000000
+vn -0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn -0.240884 0.970554 -0.000001
+vn -0.530016 0.847988 -0.000002
+vn -0.530016 0.847988 -0.000002
+vn -0.240885 0.970554 -0.000001
+vn 0.113066 0.993588 0.000000
+vn 0.113067 0.993587 0.000000
+vn 0.199028 0.979994 0.000000
+vn 0.199028 0.979994 0.000000
+vn 0.000001 0.642156 0.766574
+vn 0.000001 0.642153 0.766576
+vn 0.000002 0.511782 0.859115
+vn 0.000002 0.511782 0.859115
+vn 0.000000 0.855445 0.517893
+vn 0.000000 0.855445 0.517893
+vn -0.000000 0.998102 0.061576
+vn -0.000000 0.998102 0.061576
+vn -0.000000 1.000000 0.000000
+vn -0.000000 1.000000 0.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 1.000000
+vn 0.000000 0.000000 1.000000
+vn -0.000000 0.572793 0.819700
+vn -0.000000 0.572793 0.819700
+vn -0.000000 0.572793 0.819700
+vn -0.000000 0.572793 0.819700
+vn 0.000001 0.694861 0.719144
+vn 0.000001 0.694860 0.719145
+vn 0.000000 0.930015 0.367521
+vn 0.000000 0.930014 0.367523
+vn 0.000000 0.996920 0.078425
+vn 0.000000 0.996920 0.078425
+vn 0.000000 0.970554 0.240883
+vn 0.000000 0.970554 0.240885
+vn 0.000001 0.848001 0.529995
+vn 0.000001 0.848001 0.529995
+vn -0.000000 0.993588 -0.113066
+vn -0.000000 0.993587 -0.113066
+vn -0.000000 0.979994 -0.199028
+vn -0.000000 0.979994 -0.199028
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn 0.000000 0.000000 -1.000000
+vn -0.000006 1.000000 0.000006
+vn -0.000006 1.000000 0.000006
+vn 0.000000 1.000000 0.000012
+vn 0.000000 1.000000 0.000012
+vn 0.000002 0.519613 0.854402
+vn 0.000002 0.519613 0.854402
+vn 0.000002 0.519613 0.854402
+vn 0.000002 0.519613 0.854402
+vn -0.000000 1.000000 0.000006
+vn -0.000000 1.000000 0.000006
+vn -0.000000 1.000000 0.000006
+vn -0.000000 1.000000 0.000006
+vn 1.000000 0.000000 -0.000001
+vn 1.000000 0.000000 -0.000001
+vn 1.000000 0.000000 -0.000001
+vn 1.000000 0.000000 -0.000001
+vn -0.000006 1.000000 0.000000
+vn -0.000006 1.000000 0.000000
+vn -0.000006 1.000000 0.000000
+vn -0.000006 1.000000 0.000000
+vn -0.819683 0.572818 0.000001
+vn -0.819683 0.572818 0.000001
+vn -0.819683 0.572818 0.000001
+vn -0.819683 0.572818 0.000001
+vn -0.719151 0.694854 0.000001
+vn -0.719151 0.694854 0.000001
+vn -0.367513 0.930019 0.000000
+vn -0.367513 0.930019 0.000000
+vn -0.078424 0.996920 0.000000
+vn -0.078424 0.996920 0.000000
+vn -0.854431 0.519565 0.000000
+vn -0.854431 0.519565 0.000000
+vn -0.854431 0.519565 0.000000
+vn -0.854431 0.519565 0.000000
+vn -0.000012 1.000000 -0.000000
+vn -0.000012 1.000000 -0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -1.000000 0.000000 0.000000
+vn -0.707093 -0.000030 0.707121
+vn -0.707093 -0.000030 0.707121
+vn -0.707093 -0.000030 0.707121
+vn -0.707110 -0.000009 0.707104
+vn -0.707110 -0.000009 0.707104
+vn -0.707110 -0.000009 0.707104
+vn -0.707110 -0.000009 0.707104
+vn -0.707100 0.000014 0.707113
+vn -0.707100 0.000014 0.707113
+vn -0.707100 0.000014 0.707113
+vn -0.707130 0.000000 0.707083
+vn -0.707131 0.000000 0.707083
+vn -0.707130 0.000000 0.707083
+vn -0.707103 0.000007 0.707111
+vn -0.707103 0.000007 0.707111
+vn -0.707103 0.000007 0.707111
+vn -0.707103 0.000007 0.707111
+vn -0.707110 0.000004 0.707103
+vn -0.707110 0.000004 0.707103
+vn -0.707110 0.000004 0.707104
+vn -0.707113 0.000004 0.707101
+vn -0.707113 0.000004 0.707101
+vn -0.707113 0.000004 0.707101
+vn -0.707109 0.000004 0.707105
+vn -0.707109 0.000004 0.707105
+vn -0.707109 0.000004 0.707105
+vn -0.707110 0.000004 0.707103
+vn -0.707110 0.000004 0.707103
+vn -0.707110 0.000004 0.707103
+vn -0.707090 -0.000002 0.707124
+vn -0.707090 -0.000002 0.707124
+vn -0.707090 -0.000002 0.707124
+vn -0.707104 0.000004 0.707110
+vn -0.707104 0.000004 0.707110
+vn -0.707104 0.000004 0.707110
+vn -0.707104 0.000004 0.707110
+vn -0.707107 -0.000000 0.707106
+vn -0.707107 -0.000000 0.707106
+vn -0.707107 -0.000000 0.707106
+vn -0.707107 0.000000 0.707107
+vn -0.707107 0.000000 0.707107
+vn -0.707107 0.000000 0.707107
+vn -0.707126 0.000000 0.707087
+vn -0.707126 0.000000 0.707087
+vn -0.707126 0.000000 0.707087
+vn -0.707115 -0.000000 0.707098
+vn -0.707115 -0.000000 0.707098
+vn -0.707115 -0.000000 0.707098
+vn -0.707092 -0.000015 0.707121
+vn -0.707092 -0.000015 0.707121
+vn -0.707092 -0.000015 0.707121
+vn -0.707110 0.000001 0.707103
+vn -0.707110 0.000001 0.707103
+vn -0.707110 0.000001 0.707103
+vn -0.707110 0.000001 0.707103
+vn -0.707115 -0.000006 0.707098
+vn -0.707115 -0.000006 0.707098
+vn -0.707115 -0.000006 0.707098
+vn -0.707118 -0.000007 0.707095
+vn -0.707118 -0.000007 0.707095
+vn -0.707118 -0.000007 0.707095
+vn -0.707129 -0.000007 0.707084
+vn -0.707129 -0.000007 0.707084
+vn -0.707129 -0.000007 0.707084
+vn -0.707114 -0.000012 0.707100
+vn -0.707114 -0.000012 0.707100
+vn -0.707114 -0.000012 0.707100
+vn -0.707092 -0.000027 0.707122
+vn -0.707092 -0.000027 0.707122
+vn -0.707092 -0.000027 0.707122
+vn -0.707113 0.000003 0.707100
+vn -0.707114 0.000003 0.707100
+vn -0.707113 0.000003 0.707100
+vn -0.707113 0.000003 0.707100
+vn -0.707126 0.000000 0.707088
+vn -0.707126 0.000000 0.707087
+vn -0.707126 0.000000 0.707088
+vn -0.707107 -0.000014 0.707107
+vn -0.707107 -0.000014 0.707107
+vn -0.707107 -0.000014 0.707107
+vn -0.707105 -0.000013 0.707108
+vn -0.707105 -0.000013 0.707108
+vn -0.707105 -0.000013 0.707108
+vn -0.707105 -0.000013 0.707108
+vn -0.707186 -0.000134 0.707028
+vn -0.707186 -0.000134 0.707027
+vn -0.707186 -0.000134 0.707027
+vn -0.707067 0.000000 0.707147
+vn -0.707067 0.000000 0.707147
+vn -0.707067 0.000000 0.707147
+s 1
+f 4/1/1 3/2/2 27/3/3 28/4/4
+f 5/5/5 4/1/1 28/4/4 29/6/6
+f 6/7/7 5/5/5 29/6/6 30/8/8
+f 6/7/7 30/8/8 31/10/9 7/9/10
+s 2
+f 16/11/11 10/12/12 20/13/13 21/14/14
+f 16/11/11 21/14/14 22/16/15 17/15/16
+f 17/15/16 22/16/15 23/17/17 12/18/18
+s 3
+f 4/19/19 41/20/20 42/21/21 3/22/22
+f 5/23/23 40/24/24 41/20/20 4/19/19
+f 6/25/25 39/26/26 40/24/24 5/23/23
+f 39/26/26 6/25/25 7/27/27 38/28/28
+s off
+f 1/29/29 50/30/30 51/31/31 2/32/32
+f 14/33/33 37/34/34 38/28/35 7/27/36
+s 3
+f 13/37/37 43/38/38 44/39/39 15/40/40
+f 11/36/41 15/40/40 44/39/39 45/35/42
+s 4
+f 16/41/43 48/42/44 49/43/45 10/44/46
+f 48/42/44 16/41/43 17/45/47 47/46/48
+f 47/46/48 17/45/47 12/47/49 46/48/50
+s off
+f 9/49/51 8/51/52 35/52/53 36/50/54
+s 3
+f 13/37/37 3/22/22 42/21/21 43/38/38
+s 5
+f 1/29/55 10/44/56 49/43/57 50/30/58
+s off
+f 12/47/59 11/36/60 45/35/61 46/48/62
+f 37/34/63 14/33/64 9/49/65 36/50/66
+f 33/53/67 34/54/68 8/55/69 9/56/70
+f 14/58/71 32/57/72 33/53/73 9/56/74
+f 7/9/75 31/10/76 32/57/77 14/58/78
+s 1
+f 13/59/79 26/60/80 27/3/3 3/2/2
+f 25/61/81 26/60/80 13/59/79 15/62/82
+f 11/63/83 24/64/84 25/61/81 15/62/82
+s off
+f 23/17/85 24/64/86 11/63/87 12/18/88
+s 5
+f 1/65/55 19/66/89 20/13/90 10/12/56
+s off
+f 18/67/91 19/66/92 1/65/93 2/68/94
+f 21/69/95 20/70/96 18/71/97
+f 18/71/98 52/73/99 22/72/100 21/69/101
+f 52/73/102 23/74/103 22/72/104
+f 24/75/105 23/74/106 52/73/107
+f 53/76/108 25/77/109 24/75/110 52/73/111
+f 26/78/112 25/77/113 53/76/114
+f 27/79/115 26/78/116 53/76/117
+f 28/80/118 27/79/119 53/76/120
+f 29/81/121 28/80/122 53/76/123
+f 30/82/124 29/81/125 53/76/126
+f 53/76/127 34/84/128 31/83/129 30/82/130
+f 32/85/131 31/83/132 34/84/133
+f 34/84/134 33/86/135 32/85/136
+f 18/71/137 20/70/138 19/87/139
+f 35/89/140 37/88/141 36/106/142
+f 38/90/143 37/88/144 35/89/145
+f 54/91/146 39/92/147 38/90/148 35/89/149
+f 40/93/150 39/92/151 54/91/152
+f 41/94/153 40/93/154 54/91/155
+f 42/95/156 41/94/157 54/91/158
+f 43/96/159 42/95/160 54/91/161
+f 44/97/162 43/96/163 54/91/164
+f 54/91/165 55/99/166 45/98/167 44/97/168
+f 46/100/169 45/98/170 55/99/171
+f 55/99/172 47/101/173 46/100/174
+f 51/102/175 48/103/176 47/101/177 55/99/178
+f 49/104/179 48/103/180 51/102/181
+f 49/104/182 51/102/183 50/105/184
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.sfa b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.sfa
new file mode 100644
index 000000000..b94b93469
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/sampledata/models/frame_upper_right.sfa
@@ -0,0 +1,50 @@
+{
+ materials: [
+ {
+ name: 'unlit_material',
+ parameters: [
+ {
+ baseColor: 'frame_base',
+ },
+ {
+ baseColorTint: [
+ 1,
+ 1,
+ 1,
+ 1,
+ ],
+ },
+ {
+ metallic: 1,
+ },
+ {
+ roughness: 1,
+ },
+ {
+ opacity: null,
+ },
+ ],
+ source: 'build/sceneform_sdk/default_materials/obj_material.sfm',
+ },
+ ],
+ model: {
+ attributes: [
+ 'Position',
+ 'TexCoord',
+ 'Orientation',
+ ],
+ collision: {},
+ file: 'sampledata/models/frame_upper_right.obj',
+ name: 'frame_upper_right',
+ recenter: 'root',
+ scale: 1.0006200000000001,
+ },
+ samplers: [
+ {
+ file: 'sampledata/models\\frame_base.png',
+ name: 'frame_base',
+ pipeline_name: 'frame_base.png',
+ },
+ ],
+ version: '0.54:2',
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/AndroidManifest.xml b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..199fb3cba
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/AndroidManifest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/default.jpg b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/default.jpg
new file mode 100644
index 000000000..d3241668f
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/default.jpg differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_lower_left.sfb b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_lower_left.sfb
new file mode 100644
index 000000000..5120d5950
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_lower_left.sfb differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_lower_right.sfb b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_lower_right.sfb
new file mode 100644
index 000000000..a40662459
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_lower_right.sfb differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_upper_left.sfb b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_upper_left.sfb
new file mode 100644
index 000000000..9b6e2e4ad
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_upper_left.sfb differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_upper_right.sfb b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_upper_right.sfb
new file mode 100644
index 000000000..e65a5ac6a
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/models/frame_upper_right.sfb differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/sample_database.imgdb b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/sample_database.imgdb
new file mode 100644
index 000000000..dba77b428
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/sample_database.imgdb differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/server.jpg b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/server.jpg
new file mode 100644
index 000000000..a9fec5fda
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/server.jpg differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/test.jpg b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/test.jpg
new file mode 100644
index 000000000..5158148ae
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/assets/test.jpg differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageActivity.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageActivity.java
new file mode 100644
index 000000000..2d8b1b2b3
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageActivity.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.ar.sceneform.samples.augmentedimage;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentTransaction;
+
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.google.ar.core.Anchor;
+import com.google.ar.core.AugmentedImage;
+import com.google.ar.core.Frame;
+import com.google.ar.core.Pose;
+import com.google.ar.core.Session;
+import com.google.ar.core.TrackingState;
+import com.google.ar.core.exceptions.NotYetAvailableException;
+import com.google.ar.sceneform.AnchorNode;
+import com.google.ar.sceneform.FrameTime;
+import com.google.ar.sceneform.rendering.ViewRenderable;
+import com.google.ar.sceneform.samples.augmentedimage.flowgate.flowgateClient;
+import com.google.ar.sceneform.ux.ArFragment;
+import com.google.mlkit.vision.common.InputImage;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * This application demonstrates using augmented images to place anchor nodes. app to include image
+ * tracking functionality.
+ *
+ *
In this example, we assume all images are static or moving slowly with a large occupation of
+ * the screen. If the target is actively moving, we recommend to check
+ * ArAugmentedImage_getTrackingMethod() and render only when the tracking method equals to
+ * AR_AUGMENTED_IMAGE_TRACKING_METHOD_FULL_TRACKING. See details in Recognize and Augment
+ * Images.
+ */
+public class AugmentedImageActivity extends AppCompatActivity implements AugmentedImageFragment.OnCompleteListener {
+ private ArFragment arFragment;
+ private TextView serverTextview;
+ private ViewRenderable serverRenderable;
+ private AnchorNode serverNode;
+ private Boolean isScanSuccess = false;
+ private Boolean isPause = true;
+
+ flowgateClient fc = new flowgateClient("202.121.180.32", "admin", "Ar_InDataCenter_450");
+
+ // Augmented image and its associated center pose anchor, keyed by the augmented image in
+ // the database.
+ private Map augmentedImageMap = new HashMap<>();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ // Create new fragment and transaction
+ arFragment = new AugmentedImageFragment();
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+
+ // Replace whatever is in the fragment_container view with this fragment,
+ // and add the transaction to the back stack
+ transaction.replace(R.id.ux_fragment, arFragment);
+ transaction.addToBackStack(null);
+ // Commit the transaction
+ transaction.commit();
+
+ // arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.ux_fragment);
+
+ Button reset = findViewById(R.id.button1);
+ // Reset the app status.
+ reset.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ isScanSuccess = false;
+ serverNode = null;
+ if (serverTextview != null){
+ serverTextview.setText("");
+ }
+ TextView dialog = findViewById(R.id.disp1);
+ String text = "Resetting";
+ dialog.setText(text);
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.detach(arFragment);
+ transaction.attach(arFragment);
+ transaction.commit();
+ }
+ });
+
+ Button pause = findViewById(R.id.button2);
+ // Pause/continue the current session.
+ pause.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String text;
+ if (isPause){
+ onPause();
+ text = "Continue";
+ } else {
+ onResume();
+ text = "Pause";
+ }
+ isPause = !isPause;
+ pause.setText(text);
+ }
+ });
+ }
+
+ @Override
+ public void onComplete() {
+ TextView dialog = findViewById(R.id.disp1);
+ String text = "Detecting";
+ dialog.setText(text);
+ // Build the 2D renderable for text views of server.
+ ViewRenderable.builder()
+ .setView(this, R.layout.server_view)
+ .build()
+ .thenAccept(renderable -> serverRenderable = renderable);
+
+ arFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdateFrame);
+ }
+
+ /**
+ * Registered with the Sceneform Scene object, this method is called at the start of each frame.
+ *
+ * @param frameTime - time since last frame.
+ */
+ private void onUpdateFrame(FrameTime frameTime) {
+ Frame frame = arFragment.getArSceneView().getArFrame();
+ Session session = arFragment.getArSceneView().getSession();
+
+ if (session == null || frame == null) {
+ return;
+ }
+
+ // Let the fragment update its state first.
+ arFragment.onUpdate(frameTime);
+
+ // If ARCore is not tracking yet, then don't process anything.
+ if (frame.getCamera().getTrackingState() != TrackingState.TRACKING) {
+ return;
+ }
+
+ serverTextview = (TextView) serverRenderable.getView();
+
+ // Scan once; and once the information is obtained, no scanning before reset.
+ if (!isScanSuccess) {
+ /*if (this.serverNode != null) {
+ serverNode.getAnchor().detach();
+ serverNode.setParent(null);
+ serverNode.setRenderable(null);
+ serverNode = null;
+ augmentedImageMap.forEach((image, node) -> {
+ arFragment.getArSceneView().getScene().removeChild(node);
+ node = null;
+ });
+ augmentedImageMap.clear();
+ }*/
+ // Send frame image and scan barcode.
+ try (final Image image = arFragment.getArSceneView().getArFrame().acquireCameraImage()) {
+ if (image.getFormat() == ImageFormat.YUV_420_888) {
+ Bitmap bitmapImage = YUV420toBitmap.getBitmap(image);
+ BarcodeScan barScanning = new BarcodeScan();
+ InputImage inputImage = InputImage.fromBitmap(bitmapImage, 0);
+
+ Context context = this.getApplicationContext();
+
+ TextView dialog = findViewById(R.id.disp1);
+ barScanning.scanBarcodes(inputImage, fc, context, serverTextview, dialog);
+ image.close();
+ }
+ } catch (NotYetAvailableException e) {
+ Log.e("NYA", "Not yet available");
+ e.printStackTrace();
+ }
+ }
+
+ isScanSuccess = (serverTextview.getText().toString().length() != 0);
+
+ // Image recognition after barcode scanning.
+ if (isScanSuccess) {
+ Collection updatedAugmentedImages =
+ frame.getUpdatedTrackables(AugmentedImage.class);
+ for (AugmentedImage augmentedImage : updatedAugmentedImages) {
+ TextView dialog = findViewById(R.id.disp1);
+ String text;
+ switch (augmentedImage.getTrackingState()) {
+ case PAUSED:
+ // When an image is in PAUSED state, but the camera is not PAUSED, it has been detected,
+ // but not yet tracked.
+ text = "Detected the paused Image " + augmentedImage.getIndex();
+ dialog.setText(text);
+ break;
+
+ case TRACKING:
+ // Create a new anchor for newly found images.
+ if (!augmentedImageMap.containsKey(augmentedImage)) {
+ AugmentedImageNode node = new AugmentedImageNode();
+ node.setImage(augmentedImage, this);
+ augmentedImageMap.put(augmentedImage, node);
+ arFragment.getArSceneView().getScene().addChild(node);
+ text = "Device detected and frame drawn";
+ dialog.setText(text);
+
+ // Put the server's information near the cabinet.
+ if (this.serverNode == null) {
+ float[] pos = {augmentedImage.getCenterPose().tx() + augmentedImage.getExtentX(), augmentedImage.getCenterPose().ty(), augmentedImage.getCenterPose().tz()};
+ float[] rotation = {0, 0, 0, 90};
+ Anchor anchor = session.createAnchor(new Pose(pos, rotation));
+ serverNode = new AnchorNode(anchor);
+ serverNode.setRenderable(serverRenderable);
+ serverNode.setParent(arFragment.getArSceneView().getScene());
+ }
+ }
+ break;
+
+ case STOPPED:
+ augmentedImageMap.remove(augmentedImage);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageFragment.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageFragment.java
new file mode 100644
index 000000000..ca4f4ac5c
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageFragment.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.ar.sceneform.samples.augmentedimage;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.google.ar.core.AugmentedImageDatabase;
+import com.google.ar.core.Config;
+import com.google.ar.core.Session;
+import com.google.ar.sceneform.samples.common.helpers.SnackbarHelper;
+import com.google.ar.sceneform.ux.ArFragment;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Extend the ArFragment to customize the ARCore session configuration to include Augmented Images.
+ */
+public class AugmentedImageFragment extends ArFragment {
+ private static final String TAG = "AugmentedImageFragment";
+
+ // This is the name of the image in the sample database. A copy of the image is in the assets
+ // directory. Opening this image on your computer is a good quick way to test the augmented image
+ // matching.
+ private static final String DEFAULT_IMAGE_NAME = "test.jpg";
+
+ // This is a pre-created database containing the sample image.
+ private static final String SAMPLE_IMAGE_DATABASE = "sample_database.imgdb";
+
+ // Augmented image configuration and rendering.
+ // Load a single image (true) or a pre-generated image database (false).
+ private static final boolean USE_SINGLE_IMAGE = true;
+
+ // Do a runtime check for the OpenGL level available at runtime to avoid Sceneform crashing the
+ // application.
+ private static final double MIN_OPENGL_VERSION = 3.0;
+
+ private OnCompleteListener mListener;
+
+ public static interface OnCompleteListener {
+ public abstract void onComplete();
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ // Check for Sceneform being supported on this device. This check will be integrated into
+ // Sceneform eventually.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
+ Log.e(TAG, "Sceneform requires Android N or later");
+ SnackbarHelper.getInstance()
+ .showError(getActivity(), "Sceneform requires Android N or later");
+ }
+
+ String openGlVersionString =
+ ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE))
+ .getDeviceConfigurationInfo()
+ .getGlEsVersion();
+ if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) {
+ Log.e(TAG, "Sceneform requires OpenGL ES 3.0 or later");
+ SnackbarHelper.getInstance()
+ .showError(getActivity(), "Sceneform requires OpenGL ES 3.0 or later");
+ }
+
+ try {
+ this.mListener = (OnCompleteListener)context;
+ }
+ catch (final ClassCastException e) {
+ throw new ClassCastException(context.toString() + " must implement OnCompleteListener");
+ }
+ }
+
+ @Override
+ public View onCreateView(
+ LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = super.onCreateView(inflater, container, savedInstanceState);
+
+ // Turn off the plane discovery since we're only looking for images
+ getPlaneDiscoveryController().hide();
+ getPlaneDiscoveryController().setInstructionView(null);
+ getArSceneView().getPlaneRenderer().setEnabled(false);
+ return view;
+ }
+
+ @Override
+ protected Config getSessionConfiguration(Session session) {
+ Config config = super.getSessionConfiguration(session);
+ if (!setupAugmentedImageDatabase(config, session)) {
+ SnackbarHelper.getInstance()
+ .showError(getActivity(), "Could not setup augmented image database");
+ }
+ // Set Auto focus of the camera.
+ config.setFocusMode(Config.FocusMode.AUTO);
+ mListener.onComplete();
+ return config;
+ }
+
+ private boolean setupAugmentedImageDatabase(Config config, Session session) {
+ AugmentedImageDatabase augmentedImageDatabase;
+
+ AssetManager assetManager = getContext() != null ? getContext().getAssets() : null;
+ if (assetManager == null) {
+ Log.e(TAG, "Context is null, cannot intitialize image database.");
+ return false;
+ }
+
+ // There are two ways to configure an AugmentedImageDatabase:
+ // 1. Add Bitmap to DB directly
+ // 2. Load a pre-built AugmentedImageDatabase
+ // Option 2) has
+ // * shorter setup time
+ // * doesn't require images to be packaged in apk.
+ if (USE_SINGLE_IMAGE) {
+ Bitmap augmentedImageBitmap = loadAugmentedImageBitmap(assetManager);
+ if (augmentedImageBitmap == null) {
+ return false;
+ }
+
+ augmentedImageDatabase = new AugmentedImageDatabase(session);
+ augmentedImageDatabase.addImage(DEFAULT_IMAGE_NAME, augmentedImageBitmap, (float) 0.076);
+ // If the physical size of the image is known, you can instead use:
+ // augmentedImageDatabase.addImage("image_name", augmentedImageBitmap, widthInMeters);
+ // This will improve the initial detection speed. ARCore will still actively estimate the
+ // physical size of the image as it is viewed from multiple viewpoints.
+ } else {
+ // This is an alternative way to initialize an AugmentedImageDatabase instance,
+ // load a pre-existing augmented image database.
+ try (InputStream is = getContext().getAssets().open(SAMPLE_IMAGE_DATABASE)) {
+ augmentedImageDatabase = AugmentedImageDatabase.deserialize(session, is);
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception loading augmented image database.", e);
+ return false;
+ }
+ }
+
+ config.setAugmentedImageDatabase(augmentedImageDatabase);
+ return true;
+ }
+
+ private Bitmap loadAugmentedImageBitmap(AssetManager assetManager) {
+ try (InputStream is = assetManager.open(DEFAULT_IMAGE_NAME)) {
+ return BitmapFactory.decodeStream(is);
+ } catch (IOException e) {
+ Log.e(TAG, "IO exception loading augmented image bitmap.", e);
+ }
+ return null;
+ }
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageNode.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageNode.java
new file mode 100644
index 000000000..dc6521d19
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/AugmentedImageNode.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2018 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.ar.sceneform.samples.augmentedimage;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.google.ar.core.AugmentedImage;
+import com.google.ar.sceneform.AnchorNode;
+import com.google.ar.sceneform.Node;
+import com.google.ar.sceneform.collision.Box;
+import com.google.ar.sceneform.math.Vector3;
+import com.google.ar.sceneform.rendering.Color;
+import com.google.ar.sceneform.rendering.Material;
+import com.google.ar.sceneform.rendering.MaterialFactory;
+import com.google.ar.sceneform.rendering.ModelRenderable;
+import com.google.ar.sceneform.rendering.Renderable;
+import com.google.ar.sceneform.rendering.RenderableDefinition;
+import com.google.ar.sceneform.rendering.ShapeFactory;
+import com.google.ar.sceneform.rendering.Vertex;
+import com.google.ar.sceneform.utilities.AndroidPreconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Node for rendering an augmented image. The image is framed by placing the virtual picture frame
+ * at the corners of the augmented image trackable.
+ */
+@SuppressWarnings({"AndroidApiChecker"})
+public class AugmentedImageNode extends AnchorNode {
+
+ private static final String TAG = "AugmentedImageNode";
+
+ // The augmented image represented by this node.
+ private AugmentedImage image;
+
+ // Two kinds of bars: one vertical, one horizontal;
+ // two vertical bars & two horizontal bars form a frame
+ private static ModelRenderable[] bar = new ModelRenderable[2];
+ private static ModelRenderable[] dividerBar = new ModelRenderable[1];
+
+ public AugmentedImageNode() {
+ // Do nothing upon construction
+ }
+
+ /**
+ * Called when the AugmentedImage is detected and should be rendered. A Sceneform node tree is
+ * created based on an Anchor created from the image. The corners are then positioned based on the
+ * extents of the image. There is no need to worry about world coordinates since everything is
+ * relative to the center of the image, which is the parent node of the corners.
+ */
+ @SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"})
+ public void setImage(AugmentedImage image, Context context) {
+ this.image = image;
+
+ Log.d(TAG, "setImage begins");
+
+ setAnchor(image.createAnchor(image.getCenterPose()));
+
+ // width of frame bar
+ float frameWidth = image.getExtentX()/20;
+ ArrayList triangleIndices = getTriangleIndices();
+ // position of vertices of bars
+ ArrayList[] vertices= new ArrayList[2];
+ vertices[0] = getCubeVertices(new Vector3(image.getExtentX() + 2 * frameWidth, 0.0f, frameWidth), new Vector3(0.0f, 0.0f, 0.0f));
+ vertices[1] = getCubeVertices(new Vector3(frameWidth, 0.0f, image.getExtentZ() + 2 * frameWidth), new Vector3(0.0f, 0.0f, 0.0f));
+ ArrayList dividerVertices = getCubeVertices(new Vector3(image.getExtentX(), 0.0f, frameWidth/5), new Vector3(0.0f, 0.0f, 0.0f));
+ // color of frame
+ Color c = new Color(android.graphics.Color.BLUE);
+ MaterialFactory.makeTransparentWithColor(context, c).thenAccept(
+ // get the bars
+ material -> {
+ Log.d(TAG, "materials set");
+
+ RenderableDefinition.Submesh submesh =
+ RenderableDefinition.Submesh.builder().setTriangleIndices(triangleIndices).setMaterial(material).build();
+
+ RenderableDefinition[] renderableDefinition = new RenderableDefinition[2];
+ renderableDefinition[0] =
+ RenderableDefinition.builder()
+ .setVertices(vertices[0])
+ .setSubmeshes(Arrays.asList(submesh))
+ .build();
+ renderableDefinition[1] =
+ RenderableDefinition.builder()
+ .setVertices(vertices[1])
+ .setSubmeshes(Arrays.asList(submesh))
+ .build();
+ RenderableDefinition[] dividerRenderableDefinition = new RenderableDefinition[1];
+ dividerRenderableDefinition[0] =
+ RenderableDefinition.builder()
+ .setVertices(dividerVertices)
+ .setSubmeshes(Arrays.asList(submesh))
+ .build();
+
+ try {
+
+ bar[0] = ModelRenderable.builder().setSource(renderableDefinition[0]).build().get();
+ bar[1] = ModelRenderable.builder().setSource(renderableDefinition[1]).build().get();
+ dividerBar[0] = ModelRenderable.builder().setSource(dividerRenderableDefinition[0]).build().get();
+ } catch (ExecutionException e) {
+ Log.e(TAG, "ExecutionException when set image");
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException when set image");
+ e.printStackTrace();
+ }
+
+ // draw the frame
+ Vector3 localPosition = new Vector3();
+ Node cornerNode;
+
+ localPosition.set(0, 0.0f, 0.5f * image.getExtentZ());
+ cornerNode = new Node();
+ cornerNode.setParent(this);
+ cornerNode.setLocalPosition(localPosition);
+ cornerNode.setRenderable(bar[0]);
+
+ localPosition.set(0, 0.0f, -0.5f * image.getExtentZ());
+ cornerNode = new Node();
+ cornerNode.setParent(this);
+ cornerNode.setLocalPosition(localPosition);
+ cornerNode.setRenderable(bar[0]);
+
+ localPosition.set(0.5f * image.getExtentX(), 0.0f, 0);
+ cornerNode = new Node();
+ cornerNode.setParent(this);
+ cornerNode.setLocalPosition(localPosition);
+ cornerNode.setRenderable(bar[1]);
+
+ localPosition.set(-0.5f * image.getExtentX(), 0.0f, 0);
+ cornerNode = new Node();
+ cornerNode.setParent(this);
+ cornerNode.setLocalPosition(localPosition);
+ cornerNode.setRenderable(bar[1]);
+
+
+ float intervalNum = 14;
+ float interval = image.getExtentZ()/intervalNum;
+ float bottom = -0.5f * image.getExtentZ() + interval;
+
+ for(int i = 1; i < intervalNum; i++){
+ localPosition.set(0, 0.0f, bottom + i * interval);
+ cornerNode = new Node();
+ cornerNode.setParent(this);
+ cornerNode.setLocalPosition(localPosition);
+ cornerNode.setRenderable(dividerBar[0]);
+ }
+
+ });
+ }
+
+ public AugmentedImage getImage() {
+ return image;
+ }
+
+ // copy from ShapeFactory
+ // get eight vertices of the cube according to its size and center
+ private static ArrayList getCubeVertices(Vector3 size, Vector3 center){
+ AndroidPreconditions.checkMinAndroidApiLevel();
+
+ Vector3 extents = size.scaled(0.5f);
+
+ Vector3 p0 = Vector3.add(center, new Vector3(-extents.x, -extents.y, extents.z));
+ Vector3 p1 = Vector3.add(center, new Vector3(extents.x, -extents.y, extents.z));
+ Vector3 p2 = Vector3.add(center, new Vector3(extents.x, -extents.y, -extents.z));
+ Vector3 p3 = Vector3.add(center, new Vector3(-extents.x, -extents.y, -extents.z));
+ Vector3 p4 = Vector3.add(center, new Vector3(-extents.x, extents.y, extents.z));
+ Vector3 p5 = Vector3.add(center, new Vector3(extents.x, extents.y, extents.z));
+ Vector3 p6 = Vector3.add(center, new Vector3(extents.x, extents.y, -extents.z));
+ Vector3 p7 = Vector3.add(center, new Vector3(-extents.x, extents.y, -extents.z));
+
+ Vector3 up = Vector3.up();
+ Vector3 down = Vector3.down();
+ Vector3 front = Vector3.forward();
+ Vector3 back = Vector3.back();
+ Vector3 left = Vector3.left();
+ Vector3 right = Vector3.right();
+
+ Vertex.UvCoordinate uv00 = new Vertex.UvCoordinate(0.0f, 0.0f);
+ Vertex.UvCoordinate uv10 = new Vertex.UvCoordinate(1.0f, 0.0f);
+ Vertex.UvCoordinate uv01 = new Vertex.UvCoordinate(0.0f, 1.0f);
+ Vertex.UvCoordinate uv11 = new Vertex.UvCoordinate(1.0f, 1.0f);
+
+ ArrayList vertices =
+ new ArrayList<>(
+ Arrays.asList(
+ // Bottom
+ Vertex.builder().setPosition(p0).setNormal(down).setUvCoordinate(uv01).build(),
+ Vertex.builder().setPosition(p1).setNormal(down).setUvCoordinate(uv11).build(),
+ Vertex.builder().setPosition(p2).setNormal(down).setUvCoordinate(uv10).build(),
+ Vertex.builder().setPosition(p3).setNormal(down).setUvCoordinate(uv00).build(),
+ // Left
+ Vertex.builder().setPosition(p7).setNormal(left).setUvCoordinate(uv01).build(),
+ Vertex.builder().setPosition(p4).setNormal(left).setUvCoordinate(uv11).build(),
+ Vertex.builder().setPosition(p0).setNormal(left).setUvCoordinate(uv10).build(),
+ Vertex.builder().setPosition(p3).setNormal(left).setUvCoordinate(uv00).build(),
+ // Front
+ Vertex.builder().setPosition(p4).setNormal(front).setUvCoordinate(uv01).build(),
+ Vertex.builder().setPosition(p5).setNormal(front).setUvCoordinate(uv11).build(),
+ Vertex.builder().setPosition(p1).setNormal(front).setUvCoordinate(uv10).build(),
+ Vertex.builder().setPosition(p0).setNormal(front).setUvCoordinate(uv00).build(),
+ // Back
+ Vertex.builder().setPosition(p6).setNormal(back).setUvCoordinate(uv01).build(),
+ Vertex.builder().setPosition(p7).setNormal(back).setUvCoordinate(uv11).build(),
+ Vertex.builder().setPosition(p3).setNormal(back).setUvCoordinate(uv10).build(),
+ Vertex.builder().setPosition(p2).setNormal(back).setUvCoordinate(uv00).build(),
+ // Right
+ Vertex.builder().setPosition(p5).setNormal(right).setUvCoordinate(uv01).build(),
+ Vertex.builder().setPosition(p6).setNormal(right).setUvCoordinate(uv11).build(),
+ Vertex.builder().setPosition(p2).setNormal(right).setUvCoordinate(uv10).build(),
+ Vertex.builder().setPosition(p1).setNormal(right).setUvCoordinate(uv00).build(),
+ // Top
+ Vertex.builder().setPosition(p7).setNormal(up).setUvCoordinate(uv01).build(),
+ Vertex.builder().setPosition(p6).setNormal(up).setUvCoordinate(uv11).build(),
+ Vertex.builder().setPosition(p5).setNormal(up).setUvCoordinate(uv10).build(),
+ Vertex.builder().setPosition(p4).setNormal(up).setUvCoordinate(uv00).build()));
+
+ return vertices;
+ }
+
+ // copy from ShapeFactory
+ private static ArrayList getTriangleIndices(){
+ final int COORDS_PER_TRIANGLE = 3;
+ final int numSides = 6;
+ final int verticesPerSide = 4;
+ final int trianglesPerSide = 2;
+
+ ArrayList triangleIndices =
+ new ArrayList<>(numSides * trianglesPerSide * COORDS_PER_TRIANGLE);
+ for (int i = 0; i < numSides; i++) {
+ // First triangle for this side.
+ triangleIndices.add(3 + verticesPerSide * i);
+ triangleIndices.add(1 + verticesPerSide * i);
+ triangleIndices.add(0 + verticesPerSide * i);
+
+ // Second triangle for this side.
+ triangleIndices.add(3 + verticesPerSide * i);
+ triangleIndices.add(2 + verticesPerSide * i);
+ triangleIndices.add(1 + verticesPerSide * i);
+ }
+ return triangleIndices;
+ }
+
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/BarcodeScan.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/BarcodeScan.java
new file mode 100644
index 000000000..c911f0fc2
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/BarcodeScan.java
@@ -0,0 +1,73 @@
+package com.google.ar.sceneform.samples.augmentedimage;
+
+import android.app.Activity;
+import android.content.Context;
+import android.util.Log;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+import com.google.android.gms.tasks.Task;
+import com.google.mlkit.vision.barcode.Barcode;
+import com.google.mlkit.vision.barcode.BarcodeScanner;
+import com.google.mlkit.vision.barcode.BarcodeScannerOptions;
+import com.google.mlkit.vision.barcode.BarcodeScanning;
+import com.google.mlkit.vision.common.InputImage;
+
+import java.util.List;
+
+import com.google.ar.sceneform.samples.augmentedimage.flowgate.flowgateClient;
+
+public class BarcodeScan {
+ private static final String TAG = "Barcode Detect";
+ public void scanBarcodes(InputImage image, flowgateClient fc, Context context, TextView textView, TextView dialog) {
+ BarcodeScannerOptions options =
+ new BarcodeScannerOptions.Builder()
+ .setBarcodeFormats(Barcode.FORMAT_CODE_128)
+ .build();
+
+ BarcodeScanner scanner = BarcodeScanning.getClient();
+ // Or, to specify the formats to recognize:
+ Log.d(TAG, "set options");
+// BarcodeScanner scanner = BarcodeScanning.getClient(options);
+
+ Log.d(TAG, "getClient");
+ Task> result = scanner.process(image)
+ .addOnSuccessListener(new OnSuccessListener>() {
+ @Override
+ public void onSuccess(List barcodes) {
+ // Task completed successfully
+ // [START_EXCLUDE]
+ // [START get_barcodes]
+ for (Barcode barcode: barcodes) {
+ String rawValue = barcode.getRawValue();
+ Log.d(TAG, "The id is: " + rawValue);
+ String text = "Barcode detected";
+ dialog.setText(text);
+
+ Thread t = new Thread(){
+ @Override
+ public void run(){
+ fc.getAssetByIdOnScreen(context, rawValue, textView);
+ }
+ };
+ t.start();
+ try{
+ t.join();
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+ }
+ })
+ .addOnFailureListener(new OnFailureListener() {
+ @Override
+ public void onFailure(@NonNull Exception e) {
+ Log.d(TAG, "Failure");
+ }
+ });
+ }
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/YUV420toBitmap.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/YUV420toBitmap.java
new file mode 100644
index 000000000..1298ef9e9
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/YUV420toBitmap.java
@@ -0,0 +1,65 @@
+package com.google.ar.sceneform.samples.augmentedimage;
+
+import android.content.ContentValues;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.media.Image;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.google.ar.core.exceptions.NotYetAvailableException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Convert the YUV420 image captured by acquireCameraImage() into bitmap
+ */
+public class YUV420toBitmap {
+ public static Bitmap getBitmap(Image image) {
+ byte[] byteArray;
+ byteArray = NV21toJPEG(YUV420toNV21(image), image.getWidth(), image.getHeight());
+ // return byteArray;
+ return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
+ }
+
+ private static byte[] NV21toJPEG(byte[] nv21, int width, int height) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
+ yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
+ return out.toByteArray();
+ }
+
+ private static byte[] YUV420toNV21(Image image) {
+ byte[] nv21;
+ // Get the three planes.
+ ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
+ ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
+ ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
+
+ int ySize = yBuffer.remaining();
+ int uSize = uBuffer.remaining();
+ int vSize = vBuffer.remaining();
+
+ nv21 = new byte[ySize + uSize + vSize];
+
+ // U and V are swapped
+ yBuffer.get(nv21, 0, ySize);
+ vBuffer.get(nv21, ySize, vSize);
+ uBuffer.get(nv21, ySize + vSize, uSize);
+
+ return nv21;
+ }
+}
\ No newline at end of file
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/MySingleton.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/MySingleton.java
new file mode 100644
index 000000000..80e6ab7e3
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/MySingleton.java
@@ -0,0 +1,68 @@
+package com.google.ar.sceneform.samples.augmentedimage.flowgate;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.LruCache;
+
+// add volley in build.gradle
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.ImageLoader;
+import com.android.volley.toolbox.Volley;
+
+/*
+* https://developer.android.com/training/volley/requestqueue
+* This class is for network request for asset info
+*/
+
+public class MySingleton {
+ private static MySingleton instance;
+ private RequestQueue requestQueue;
+ private ImageLoader imageLoader;
+ private static Context ctx;
+
+ private MySingleton(Context context) {
+ ctx = context;
+ requestQueue = getRequestQueue();
+
+ imageLoader = new ImageLoader(requestQueue,
+ new ImageLoader.ImageCache() {
+ private final LruCache
+ cache = new LruCache(20);
+
+ @Override
+ public Bitmap getBitmap(String url) {
+ return cache.get(url);
+ }
+
+ @Override
+ public void putBitmap(String url, Bitmap bitmap) {
+ cache.put(url, bitmap);
+ }
+ });
+ }
+
+ public static synchronized MySingleton getInstance(Context context) {
+ if (instance == null) {
+ instance = new MySingleton(context);
+ }
+ return instance;
+ }
+
+ public RequestQueue getRequestQueue() {
+ if (requestQueue == null) {
+ // getApplicationContext() is key, it keeps you from leaking the
+ // Activity or BroadcastReceiver if someone passes one in.
+ requestQueue = Volley.newRequestQueue(ctx.getApplicationContext());
+ }
+ return requestQueue;
+ }
+
+ public void addToRequestQueue(Request req) {
+ getRequestQueue().add(req);
+ }
+
+ public ImageLoader getImageLoader() {
+ return imageLoader;
+ }
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClient.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClient.java
new file mode 100644
index 000000000..9b72cde6c
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClient.java
@@ -0,0 +1,194 @@
+package com.google.ar.sceneform.samples.augmentedimage.flowgate;
+
+import android.content.Context;
+import android.util.Log;
+import android.widget.TextView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.URL;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class flowgateClient {
+ private static final String TAG = "flowgateClient";
+
+ private String host;
+ private String userName;
+ private String password;
+
+ private final String assetString = "/apiservice/v1/assets/";
+
+ private JSONObject token = null;
+
+ public flowgateClient(String host, String userName, String password){
+ this.host = host;
+ this.userName = userName;
+ this.password = password;
+ }
+
+ public void setHost(String newHost){
+ this.host = newHost;
+ }
+
+ public void setUserName(String newUserName){
+ this.userName = newUserName;
+ }
+
+ public void setPassword(String newPassword){
+ this.password = newPassword;
+ }
+
+ TrustManager[] trustAllCerts = new TrustManager[]{
+ new X509TrustManager() {
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ public void checkClientTrusted(
+ java.security.cert.X509Certificate[] certs, String authType) {
+ }
+ public void checkServerTrusted(
+ java.security.cert.X509Certificate[] certs, String authType) {
+ }
+ }
+ };
+
+ // Read `response` from `connection` after 200 OK
+ private StringBuilder readResponse(HttpsURLConnection connection) throws IOException {
+ InputStreamReader isr = new InputStreamReader(connection.getInputStream());
+ BufferedReader br = new BufferedReader(isr);
+ StringBuilder response = new StringBuilder();
+ String responseLine;
+ while((responseLine = br.readLine()) != null){
+ response.append(responseLine.trim());
+ }
+ return response;
+ }
+
+ // Get token
+ public String getAuthToken() {
+ try{
+ /*
+ * Existed token not expired
+ */
+ if(this.token != null){
+ long expiresTime = Long.parseLong(token.getString("expires_in"));
+ long currentTime = System.currentTimeMillis(); // unix time in milliseconds
+ Log.d("flowgateClient", expiresTime + " " + currentTime);
+ if(expiresTime - currentTime > 600000){
+ return token.getString("access_token");
+ }
+ }
+
+ /*
+ * Acquire new token
+ */
+ // install all-trusting trust manager TODO because our server has no certificate
+ try{
+ SSLContext sc = SSLContext.getInstance("SSL");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ }
+ catch (Exception e){}
+ HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
+ @Override
+ public boolean verify(String hostname, SSLSession sslSession) {
+ return true;
+ }
+ });
+
+ // set up connection
+ String authString = "/apiservice/v1/auth/token";
+ URL tokenUrl = new URL("https://" + host + authString);
+ HttpsURLConnection tokenHttpCon = (HttpsURLConnection)(tokenUrl.openConnection());
+ tokenHttpCon.setRequestMethod("POST");
+ tokenHttpCon.setDoOutput(true);
+ tokenHttpCon.setRequestProperty("Content-Type", "application/json");
+ tokenHttpCon.setRequestProperty("Accept", "application/json");
+
+ Log.d(TAG, "connect");
+
+ // send authentication info
+ String jsonUser = "{\"userName\": \""+(this.userName)+"\", "
+ + "\"password\": \""+this.password+"\"}";
+ byte[] inputUser = jsonUser.getBytes(); // StandardCharsets.UTF_8
+ OutputStream os = tokenHttpCon.getOutputStream();
+ os.write(inputUser, 0, inputUser.length);
+
+ Log.d(TAG, "auth");
+
+ // receive and set this.token and return string if success
+ tokenHttpCon.connect();
+ int responseStatus = tokenHttpCon.getResponseCode();
+ if(responseStatus == 200){
+ StringBuilder response = this.readResponse(tokenHttpCon);
+ this.token = new JSONObject(response.toString());
+ return token.getString("access_token");
+ }
+
+
+ Log.d(TAG, "return");
+
+ return null;
+ }
+ catch (IOException e){
+ Log.w("flowgateClient", "getAuthToken: IO exception when asking for token");
+ e.printStackTrace();
+ return null;
+ }
+ catch (JSONException e){
+ Log.w("flowgateClient", "getAuthToken: JSON exception when asking for token");
+ return null;
+ }
+ }
+
+ // Let flowgateClientWorker get asset information from `urlString` and display on `textView`
+ public void getAssetInfoOnScreen(Context context, String urlString, TextView textView) throws Exception {
+ class NullTokenException extends Exception{
+ public NullTokenException(){
+ super("flowgateClient: NullTokenException in getAssetInfo");
+ }
+ }
+ String receivedToken = getAuthToken();
+ if(receivedToken == null){
+ Log.w("flowgateClient", "getAssetInfo: null token");
+ throw new NullTokenException();
+ }
+
+ flowgateClientWorker worker = new flowgateClientWorker(urlString);
+ worker.displayAssetInfoOnScreen(context, receivedToken, textView);
+ }
+
+ // Get asset information by `name` and display on `textView`
+ public void getAssetByNameOnScreen(Context context, String name, TextView textView){
+ try {
+ getAssetInfoOnScreen(context, "https://" + this.host + this.assetString + "name/" + name, textView);
+ }
+ catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+
+ // Get asset information by `id` and display on `textView`
+ public void getAssetByIdOnScreen(Context context, String id, TextView textView){
+ try {
+ Log.d(TAG, "here");
+ getAssetInfoOnScreen(context, "https://" + this.host + this.assetString + id, textView);
+ }
+ catch (Exception e){
+ Log.d(TAG, e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClientActor.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClientActor.java
new file mode 100644
index 000000000..542e88d56
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClientActor.java
@@ -0,0 +1,11 @@
+package com.google.ar.sceneform.samples.augmentedimage.flowgate;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/*
+ * An interface used to determine what to do when receiving asset info
+ */
+public interface flowgateClientActor {
+ public void act(JSONObject response) throws JSONException;
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClientWorker.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClientWorker.java
new file mode 100644
index 000000000..645272eb0
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/augmentedimage/flowgate/flowgateClientWorker.java
@@ -0,0 +1,102 @@
+package com.google.ar.sceneform.samples.augmentedimage.flowgate;
+
+import android.content.Context;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.JsonObjectRequest;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/*
+ * This class is for sending *one* request to *one* url to get asset information and perform corresponding action
+ * Similar functions as `displayAssetInfoOnScreen` can be written:
+ * add arguments (e.g. location of barcode) &
+ * define a new actor to determine what to do when receiving response (e.g. display with AR)
+ */
+public class flowgateClientWorker {
+ String urlString; // `urlString` is the string of url to be requested
+
+ public flowgateClientWorker(String urlString){
+ this.urlString = urlString;
+ }
+
+ /*
+ * `context` is the class of MainActivity,
+ * `receivedToken` is the received token used for authorization
+ * `actor` is used to determine what to do when receiving response
+ * EFFECTS: obtain asset info by sending GET request to `this.urlString`,
+ * and `actor` acts
+ */
+ public void getAssetInfo(Context context, String receivedToken, flowgateClientActor actor) {
+ JsonObjectRequest jsonObjectRequest = new JsonObjectRequest
+ (Request.Method.GET, urlString, null, new Response.Listener() {
+
+ @Override
+ public void onResponse(JSONObject response) {
+ try {
+ actor.act(response);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ // Log.d("flowgateClientWorker", "setAssetInfo: " + response.toString());
+ }
+ }, new Response.ErrorListener() {
+
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ // TODO: Handle error maybe throw exception/display message on screen
+ Log.w("flowgateClientWorker",
+ "setAssetInfo: response error - " + error.toString());
+ }
+ })
+ {
+ @Override
+ public Map getHeaders() {
+ Map params = new HashMap();
+ params.put("Content-Type","application/json");
+ params.put("Authorization", "Bearer " + receivedToken);
+ params.put("Accept", "application/json");
+ return params;
+ }
+ };
+ MySingleton.getInstance(context).addToRequestQueue(jsonObjectRequest);
+ }
+
+ /*
+ * EFFECTS: obtain asset info by sending GET request to `this.urlString`,
+ * and display received info on `textView`
+ */
+ public void displayAssetInfoOnScreen(Context context, String receivedToken, TextView textView){
+ flowgateClientActor actor = new flowgateClientActor() {
+ @Override
+ public void act(JSONObject response) throws JSONException {
+ String assetNumber = response.getString("assetNumber");
+ String assetName = response.getString("assetName");
+ String category = response.getString("category");
+ String manufacturer = response.getString("manufacturer");
+ String model = response.getString("model");
+ String mountingSide = response.getString("mountingSide");
+
+ String disp =
+ assetName + "\n" +
+ "Asset Number: " + assetNumber + "\n" +
+ "Category: " + category + "\n" +
+ "Manufacturer: " + manufacturer + "\n" +
+ "Model: " + model + "\n" +
+ "Mounting side: " + mountingSide;
+
+ textView.setText(disp);
+ }
+ };
+ getAssetInfo(context, receivedToken, actor);
+ }
+
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/common/helpers/SnackbarHelper.java b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/common/helpers/SnackbarHelper.java
new file mode 100644
index 000000000..58ec82006
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/java/com/google/ar/sceneform/samples/common/helpers/SnackbarHelper.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2017 Google Inc. All Rights Reserved.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.ar.sceneform.samples.common.helpers;
+
+import android.app.Activity;
+import com.google.android.material.snackbar.BaseTransientBottomBar;
+import com.google.android.material.snackbar.Snackbar;
+import android.view.View;
+
+/**
+ * Helper to manage the sample snackbar. Hides the Android boilerplate code, and exposes simpler
+ * methods.
+ */
+public final class SnackbarHelper {
+ private static final int BACKGROUND_COLOR = 0xbf323232;
+ private static final SnackbarHelper THE_INSTANCE = new SnackbarHelper();
+ private Snackbar messageSnackbar;
+
+ private enum DismissBehavior {
+ HIDE,
+ SHOW,
+ FINISH
+ };
+
+ public static SnackbarHelper getInstance() {
+ return THE_INSTANCE;
+ }
+
+ public boolean isShowing() {
+ return messageSnackbar != null;
+ }
+
+ /** Shows a snackbar with a given message. */
+ public void showMessage(Activity activity, String message) {
+ show(activity, message, DismissBehavior.HIDE);
+ }
+
+ /** Shows a snackbar with a given message, and a dismiss button. */
+ public void showMessageWithDismiss(Activity activity, String message) {
+ show(activity, message, DismissBehavior.SHOW);
+ }
+
+ /**
+ * Shows a snackbar with a given error message. When dismissed, will finish the activity. Useful
+ * for notifying errors, where no further interaction with the activity is possible.
+ */
+ public void showError(Activity activity, String errorMessage) {
+ show(activity, errorMessage, DismissBehavior.FINISH);
+ }
+
+ /**
+ * Hides the currently showing snackbar, if there is one. Safe to call from any thread. Safe to
+ * call even if snackbar is not shown.
+ */
+ public void hide(Activity activity) {
+ activity.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (messageSnackbar != null) {
+ messageSnackbar.dismiss();
+ }
+ messageSnackbar = null;
+ }
+ });
+ }
+
+ private void show(
+ final Activity activity, final String message, final DismissBehavior dismissBehavior) {
+ activity.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ messageSnackbar =
+ Snackbar.make(
+ activity.findViewById(android.R.id.content),
+ message,
+ Snackbar.LENGTH_INDEFINITE);
+ messageSnackbar.getView().setBackgroundColor(BACKGROUND_COLOR);
+ if (dismissBehavior != DismissBehavior.HIDE) {
+ messageSnackbar.setAction(
+ "Dismiss",
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ messageSnackbar.dismiss();
+ }
+ });
+ if (dismissBehavior == DismissBehavior.FINISH) {
+ messageSnackbar.addCallback(
+ new BaseTransientBottomBar.BaseCallback() {
+ @Override
+ public void onDismissed(Snackbar transientBottomBar, int event) {
+ super.onDismissed(transientBottomBar, event);
+ activity.finish();
+ }
+ });
+ }
+ }
+ messageSnackbar.show();
+ }
+ });
+ }
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable-xxhdpi/fit_to_scan.png b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable-xxhdpi/fit_to_scan.png
new file mode 100644
index 000000000..0613db35f
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable-xxhdpi/fit_to_scan.png differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..fcf0dc09d
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable/rounded_bg.xml b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable/rounded_bg.xml
new file mode 100644
index 000000000..2e72fa117
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/drawable/rounded_bg.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/layout/activity_main.xml b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..d3bc8c195
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/layout/server_view.xml b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/layout/server_view.xml
new file mode 100644
index 000000000..ed34a1956
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/layout/server_view.xml
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/values/styles.xml b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..68b12b6c4
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/app/src/main/res/values/styles.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/ar4dcops/app_android/augmentedimage/build.gradle b/contrib/ar4dcops/app_android/augmentedimage/build.gradle
new file mode 100644
index 000000000..f3949444c
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/build.gradle
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018 Google LLC
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Top-level build file where you can add configuration options common to
+// all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ mavenLocal()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.4.1'
+ classpath 'com.google.ar.sceneform:plugin:1.15.0'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ mavenLocal()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/contrib/ar4dcops/app_android/augmentedimage/gradle.properties b/contrib/ar4dcops/app_android/augmentedimage/gradle.properties
new file mode 100644
index 000000000..9e6fce102
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/gradle.properties
@@ -0,0 +1,19 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+android.enableJetifier=true
+android.useAndroidX=true
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/contrib/ar4dcops/app_android/augmentedimage/gradle/wrapper/gradle-wrapper.jar b/contrib/ar4dcops/app_android/augmentedimage/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..87b738cbd
Binary files /dev/null and b/contrib/ar4dcops/app_android/augmentedimage/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/contrib/ar4dcops/app_android/augmentedimage/gradle/wrapper/gradle-wrapper.properties b/contrib/ar4dcops/app_android/augmentedimage/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..bcedaa3cc
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Nov 20 10:27:45 PST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/contrib/ar4dcops/app_android/augmentedimage/gradlew b/contrib/ar4dcops/app_android/augmentedimage/gradlew
new file mode 100644
index 000000000..af6708ff2
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/contrib/ar4dcops/app_android/augmentedimage/gradlew.bat b/contrib/ar4dcops/app_android/augmentedimage/gradlew.bat
new file mode 100644
index 000000000..6d57edc70
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/contrib/ar4dcops/app_android/augmentedimage/settings.gradle b/contrib/ar4dcops/app_android/augmentedimage/settings.gradle
new file mode 100644
index 000000000..c47de02a6
--- /dev/null
+++ b/contrib/ar4dcops/app_android/augmentedimage/settings.gradle
@@ -0,0 +1,17 @@
+include ':app'
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.pbxproj b/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..b7502e2ce
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.pbxproj
@@ -0,0 +1,631 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 292F8B75253F2CE2004DEE4A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292F8B74253F2CE2004DEE4A /* AppDelegate.swift */; };
+ 292F8B77253F2CE2004DEE4A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292F8B76253F2CE2004DEE4A /* ViewController.swift */; };
+ 292F8B7C253F2CE2004DEE4A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 292F8B7A253F2CE2004DEE4A /* Main.storyboard */; };
+ 292F8B7E253F2CE7004DEE4A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 292F8B7D253F2CE7004DEE4A /* Assets.xcassets */; };
+ 292F8B81253F2CE7004DEE4A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 292F8B7F253F2CE7004DEE4A /* LaunchScreen.storyboard */; };
+ 292F8B8C253F2CE8004DEE4A /* FlowARTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292F8B8B253F2CE8004DEE4A /* FlowARTests.swift */; };
+ 292F8B97253F2CE8004DEE4A /* FlowARUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292F8B96253F2CE8004DEE4A /* FlowARUITests.swift */; };
+ 2933C311255BA2E00040847E /* StatusViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2933C310255BA2E00040847E /* StatusViewController.swift */; };
+ 2933C316255BA4590040847E /* ViewController+ARSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2933C315255BA4590040847E /* ViewController+ARSessionDelegate.swift */; };
+ 2933C34F255BD8C40040847E /* art.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = 2933C34E255BD8C40040847E /* art.scnassets */; };
+ 2962552D255C2E0D00D57CF8 /* RoundedButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2962552C255C2E0D00D57CF8 /* RoundedButton.swift */; };
+ 29625532255C2F8800D57CF8 /* StartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29625531255C2F8800D57CF8 /* StartViewController.swift */; };
+ 29E1906825581B16005D9D0C /* FlowgateClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29E1906725581B16005D9D0C /* FlowgateClient.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 292F8B88253F2CE8004DEE4A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 292F8B69253F2CE2004DEE4A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 292F8B70253F2CE2004DEE4A;
+ remoteInfo = FlowAR;
+ };
+ 292F8B93253F2CE8004DEE4A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 292F8B69253F2CE2004DEE4A /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 292F8B70253F2CE2004DEE4A;
+ remoteInfo = FlowAR;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+ 292F8B71253F2CE2004DEE4A /* FlowAR.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FlowAR.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 292F8B74253F2CE2004DEE4A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 292F8B76253F2CE2004DEE4A /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
+ 292F8B7B253F2CE2004DEE4A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 292F8B7D253F2CE7004DEE4A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 292F8B80253F2CE7004DEE4A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 292F8B82253F2CE7004DEE4A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 292F8B87253F2CE8004DEE4A /* FlowARTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowARTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 292F8B8B253F2CE8004DEE4A /* FlowARTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowARTests.swift; sourceTree = ""; };
+ 292F8B8D253F2CE8004DEE4A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 292F8B92253F2CE8004DEE4A /* FlowARUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlowARUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+ 292F8B96253F2CE8004DEE4A /* FlowARUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowARUITests.swift; sourceTree = ""; };
+ 292F8B98253F2CE8004DEE4A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 2933C310255BA2E00040847E /* StatusViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusViewController.swift; sourceTree = ""; };
+ 2933C315255BA4590040847E /* ViewController+ARSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewController+ARSessionDelegate.swift"; sourceTree = ""; };
+ 2933C34E255BD8C40040847E /* art.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = art.scnassets; sourceTree = ""; };
+ 2962552C255C2E0D00D57CF8 /* RoundedButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedButton.swift; sourceTree = ""; };
+ 29625531255C2F8800D57CF8 /* StartViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartViewController.swift; sourceTree = ""; };
+ 29E1906725581B16005D9D0C /* FlowgateClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowgateClient.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 292F8B6E253F2CE2004DEE4A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 292F8B84253F2CE8004DEE4A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 292F8B8F253F2CE8004DEE4A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 292F8B68253F2CE2004DEE4A = {
+ isa = PBXGroup;
+ children = (
+ 292F8B73253F2CE2004DEE4A /* FlowAR */,
+ 292F8B8A253F2CE8004DEE4A /* FlowARTests */,
+ 292F8B95253F2CE8004DEE4A /* FlowARUITests */,
+ 292F8B72253F2CE2004DEE4A /* Products */,
+ );
+ sourceTree = "";
+ };
+ 292F8B72253F2CE2004DEE4A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 292F8B71253F2CE2004DEE4A /* FlowAR.app */,
+ 292F8B87253F2CE8004DEE4A /* FlowARTests.xctest */,
+ 292F8B92253F2CE8004DEE4A /* FlowARUITests.xctest */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 292F8B73253F2CE2004DEE4A /* FlowAR */ = {
+ isa = PBXGroup;
+ children = (
+ 2933C34E255BD8C40040847E /* art.scnassets */,
+ 2933C30F255BA2AC0040847E /* Additional View Controllers */,
+ 292F8B74253F2CE2004DEE4A /* AppDelegate.swift */,
+ 292F8B76253F2CE2004DEE4A /* ViewController.swift */,
+ 292F8B7A253F2CE2004DEE4A /* Main.storyboard */,
+ 2962552C255C2E0D00D57CF8 /* RoundedButton.swift */,
+ 292F8B7D253F2CE7004DEE4A /* Assets.xcassets */,
+ 292F8B7F253F2CE7004DEE4A /* LaunchScreen.storyboard */,
+ 292F8B82253F2CE7004DEE4A /* Info.plist */,
+ 29E1906725581B16005D9D0C /* FlowgateClient.swift */,
+ 2933C315255BA4590040847E /* ViewController+ARSessionDelegate.swift */,
+ 29625531255C2F8800D57CF8 /* StartViewController.swift */,
+ );
+ path = FlowAR;
+ sourceTree = "";
+ };
+ 292F8B8A253F2CE8004DEE4A /* FlowARTests */ = {
+ isa = PBXGroup;
+ children = (
+ 292F8B8B253F2CE8004DEE4A /* FlowARTests.swift */,
+ 292F8B8D253F2CE8004DEE4A /* Info.plist */,
+ );
+ path = FlowARTests;
+ sourceTree = "";
+ };
+ 292F8B95253F2CE8004DEE4A /* FlowARUITests */ = {
+ isa = PBXGroup;
+ children = (
+ 292F8B96253F2CE8004DEE4A /* FlowARUITests.swift */,
+ 292F8B98253F2CE8004DEE4A /* Info.plist */,
+ );
+ path = FlowARUITests;
+ sourceTree = "";
+ };
+ 2933C30F255BA2AC0040847E /* Additional View Controllers */ = {
+ isa = PBXGroup;
+ children = (
+ 2933C310255BA2E00040847E /* StatusViewController.swift */,
+ );
+ path = "Additional View Controllers";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 292F8B70253F2CE2004DEE4A /* FlowAR */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 292F8B9B253F2CE8004DEE4A /* Build configuration list for PBXNativeTarget "FlowAR" */;
+ buildPhases = (
+ 292F8B6D253F2CE2004DEE4A /* Sources */,
+ 292F8B6E253F2CE2004DEE4A /* Frameworks */,
+ 292F8B6F253F2CE2004DEE4A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = FlowAR;
+ productName = FlowAR;
+ productReference = 292F8B71253F2CE2004DEE4A /* FlowAR.app */;
+ productType = "com.apple.product-type.application";
+ };
+ 292F8B86253F2CE8004DEE4A /* FlowARTests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 292F8B9E253F2CE8004DEE4A /* Build configuration list for PBXNativeTarget "FlowARTests" */;
+ buildPhases = (
+ 292F8B83253F2CE8004DEE4A /* Sources */,
+ 292F8B84253F2CE8004DEE4A /* Frameworks */,
+ 292F8B85253F2CE8004DEE4A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 292F8B89253F2CE8004DEE4A /* PBXTargetDependency */,
+ );
+ name = FlowARTests;
+ productName = FlowARTests;
+ productReference = 292F8B87253F2CE8004DEE4A /* FlowARTests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
+ 292F8B91253F2CE8004DEE4A /* FlowARUITests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 292F8BA1253F2CE8004DEE4A /* Build configuration list for PBXNativeTarget "FlowARUITests" */;
+ buildPhases = (
+ 292F8B8E253F2CE8004DEE4A /* Sources */,
+ 292F8B8F253F2CE8004DEE4A /* Frameworks */,
+ 292F8B90253F2CE8004DEE4A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 292F8B94253F2CE8004DEE4A /* PBXTargetDependency */,
+ );
+ name = FlowARUITests;
+ productName = FlowARUITests;
+ productReference = 292F8B92253F2CE8004DEE4A /* FlowARUITests.xctest */;
+ productType = "com.apple.product-type.bundle.ui-testing";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 292F8B69253F2CE2004DEE4A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 1120;
+ LastUpgradeCheck = 1220;
+ ORGANIZATIONNAME = "周舒意";
+ TargetAttributes = {
+ 292F8B70253F2CE2004DEE4A = {
+ CreatedOnToolsVersion = 11.2.1;
+ };
+ 292F8B86253F2CE8004DEE4A = {
+ CreatedOnToolsVersion = 11.2.1;
+ TestTargetID = 292F8B70253F2CE2004DEE4A;
+ };
+ 292F8B91253F2CE8004DEE4A = {
+ CreatedOnToolsVersion = 11.2.1;
+ TestTargetID = 292F8B70253F2CE2004DEE4A;
+ };
+ };
+ };
+ buildConfigurationList = 292F8B6C253F2CE2004DEE4A /* Build configuration list for PBXProject "FlowAR" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 292F8B68253F2CE2004DEE4A;
+ productRefGroup = 292F8B72253F2CE2004DEE4A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 292F8B70253F2CE2004DEE4A /* FlowAR */,
+ 292F8B86253F2CE8004DEE4A /* FlowARTests */,
+ 292F8B91253F2CE8004DEE4A /* FlowARUITests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 292F8B6F253F2CE2004DEE4A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 292F8B81253F2CE7004DEE4A /* LaunchScreen.storyboard in Resources */,
+ 2933C34F255BD8C40040847E /* art.scnassets in Resources */,
+ 292F8B7E253F2CE7004DEE4A /* Assets.xcassets in Resources */,
+ 292F8B7C253F2CE2004DEE4A /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 292F8B85253F2CE8004DEE4A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 292F8B90253F2CE8004DEE4A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 292F8B6D253F2CE2004DEE4A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 292F8B77253F2CE2004DEE4A /* ViewController.swift in Sources */,
+ 29625532255C2F8800D57CF8 /* StartViewController.swift in Sources */,
+ 2962552D255C2E0D00D57CF8 /* RoundedButton.swift in Sources */,
+ 292F8B75253F2CE2004DEE4A /* AppDelegate.swift in Sources */,
+ 2933C316255BA4590040847E /* ViewController+ARSessionDelegate.swift in Sources */,
+ 29E1906825581B16005D9D0C /* FlowgateClient.swift in Sources */,
+ 2933C311255BA2E00040847E /* StatusViewController.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 292F8B83253F2CE8004DEE4A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 292F8B8C253F2CE8004DEE4A /* FlowARTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 292F8B8E253F2CE8004DEE4A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 292F8B97253F2CE8004DEE4A /* FlowARUITests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 292F8B89253F2CE8004DEE4A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 292F8B70253F2CE2004DEE4A /* FlowAR */;
+ targetProxy = 292F8B88253F2CE8004DEE4A /* PBXContainerItemProxy */;
+ };
+ 292F8B94253F2CE8004DEE4A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 292F8B70253F2CE2004DEE4A /* FlowAR */;
+ targetProxy = 292F8B93253F2CE8004DEE4A /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 292F8B7A253F2CE2004DEE4A /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 292F8B7B253F2CE2004DEE4A /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 292F8B7F253F2CE7004DEE4A /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 292F8B80253F2CE7004DEE4A /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 292F8B99253F2CE8004DEE4A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 292F8B9A253F2CE8004DEE4A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 292F8B9C253F2CE8004DEE4A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = G4NT6KNP66;
+ INFOPLIST_FILE = FlowAR/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.icode.FlowARLiying;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 292F8B9D253F2CE8004DEE4A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = G4NT6KNP66;
+ INFOPLIST_FILE = FlowAR/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.icode.FlowARLiying;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ 292F8B9F253F2CE8004DEE4A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 2MW7AVXSBR;
+ INFOPLIST_FILE = FlowARTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.icode.FlowARTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FlowAR.app/FlowAR";
+ };
+ name = Debug;
+ };
+ 292F8BA0253F2CE8004DEE4A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ BUNDLE_LOADER = "$(TEST_HOST)";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 2MW7AVXSBR;
+ INFOPLIST_FILE = FlowARTests/Info.plist;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.2;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.icode.FlowARTests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FlowAR.app/FlowAR";
+ };
+ name = Release;
+ };
+ 292F8BA2253F2CE8004DEE4A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 2MW7AVXSBR;
+ INFOPLIST_FILE = FlowARUITests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.icode.FlowARUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = FlowAR;
+ };
+ name = Debug;
+ };
+ 292F8BA3253F2CE8004DEE4A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 2MW7AVXSBR;
+ INFOPLIST_FILE = FlowARUITests/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.icode.FlowARUITests;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_TARGET_NAME = FlowAR;
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 292F8B6C253F2CE2004DEE4A /* Build configuration list for PBXProject "FlowAR" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 292F8B99253F2CE8004DEE4A /* Debug */,
+ 292F8B9A253F2CE8004DEE4A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 292F8B9B253F2CE8004DEE4A /* Build configuration list for PBXNativeTarget "FlowAR" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 292F8B9C253F2CE8004DEE4A /* Debug */,
+ 292F8B9D253F2CE8004DEE4A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 292F8B9E253F2CE8004DEE4A /* Build configuration list for PBXNativeTarget "FlowARTests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 292F8B9F253F2CE8004DEE4A /* Debug */,
+ 292F8BA0253F2CE8004DEE4A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 292F8BA1253F2CE8004DEE4A /* Build configuration list for PBXNativeTarget "FlowARUITests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 292F8BA2253F2CE8004DEE4A /* Debug */,
+ 292F8BA3253F2CE8004DEE4A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 292F8B69253F2CE2004DEE4A /* Project object */;
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..68744eefe
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/xcuserdata/zhoushuyi.xcuserdatad/xcschemes/xcschememanagement.plist b/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/xcuserdata/zhoushuyi.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 000000000..70b48c367
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR.xcodeproj/xcuserdata/zhoushuyi.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ SchemeUserState
+
+ FlowAR.xcscheme_^#shared#^_
+
+ orderHint
+ 0
+
+
+
+
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Additional View Controllers/StatusViewController.swift b/contrib/ar4dcops/app_ios/FlowAR/Additional View Controllers/StatusViewController.swift
new file mode 100644
index 000000000..132466d4b
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Additional View Controllers/StatusViewController.swift
@@ -0,0 +1,231 @@
+//
+// StatusViewController.swift
+// FlowAR
+//
+// Created by 周舒意 on 2020/11/11.
+// Copyright © 2020 周舒意. All rights reserved.
+//
+
+import Foundation
+import ARKit
+
+/**
+ Displayed at the top of the main interface of the app that allows users to see
+ the status of the AR experience, as well as the ability to control restarting
+ the experience altogether.
+*/
+class StatusViewController: UIViewController {
+
+ enum MessageType {
+ case trackingStateEscalation
+ case contentPlacement
+
+ static var all: [MessageType] = [
+ .trackingStateEscalation,
+ .contentPlacement
+ ]
+ }
+
+ // MARK: - IBOutlets
+
+ @IBOutlet weak private var messagePanel: UIVisualEffectView!
+
+ @IBOutlet weak private var messageLabel: UILabel!
+
+ @IBOutlet weak private var restartExperienceButton: UIButton!
+
+ @IBOutlet weak private var pausePanel: UIVisualEffectView!
+
+ @IBOutlet weak var pauseButton: UIButton!
+ // MARK: - Properties
+
+ /// Trigerred when the "Restart Experience" button is tapped.
+ var restartExperienceHandler: () -> Void = {}
+
+ var pauseSessionHandler:() -> Void = {}
+
+ /// Seconds before the timer message should fade out. Adjust if the app needs longer transient messages.
+ private let displayDuration: TimeInterval = 6
+
+ // Timer for hiding messages.
+ private var messageHideTimer: Timer?
+
+ private var timers: [MessageType: Timer] = [:]
+
+ // MARK: - Message Handling
+
+ func showMessage(_ text: String, autoHide: Bool = true) {
+ // Cancel any previous hide timer.
+ messageHideTimer?.invalidate()
+
+ messageLabel.text = text
+
+ // Make sure status is showing.
+ setMessageHidden(false, animated: true)
+
+ if autoHide {
+ messageHideTimer = Timer.scheduledTimer(withTimeInterval: displayDuration, repeats: false, block: { [weak self] _ in
+ self?.setMessageHidden(true, animated: true)
+ })
+ }
+ }
+
+ func showPause(){
+ pauseButton.setTitleColor(.black, for: .normal)
+ pauseButton.setTitleColor(.blue, for: .highlighted)
+ pauseButton.setTitle("Pause", for: .normal)
+ }
+
+ func showContinue(){
+ pauseButton.setTitleColor(.black, for: .normal)
+ pauseButton.setTitleColor(.blue, for: .highlighted)
+ pauseButton.setTitle("Continue", for: .normal)
+ }
+
+ func hidePause(){
+ pauseButton.isHidden = true
+ setPauseHidden(true, animated: true)
+ }
+
+ func unhidePause(){
+ pauseButton.isHidden = false
+ setPauseHidden(false, animated: true)
+ }
+ @IBAction func pressPauseButton(_ sender: Any) {
+ pauseSessionHandler()
+ }
+
+ func scheduleMessage(_ text: String, inSeconds seconds: TimeInterval, messageType: MessageType) {
+ cancelScheduledMessage(for: messageType)
+
+ let timer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false, block: { [weak self] timer in
+ self?.showMessage(text)
+ timer.invalidate()
+ })
+
+ timers[messageType] = timer
+ }
+
+ func cancelScheduledMessage(for messageType: MessageType) {
+ timers[messageType]?.invalidate()
+ timers[messageType] = nil
+ }
+
+ func cancelAllScheduledMessages() {
+ for messageType in MessageType.all {
+ cancelScheduledMessage(for: messageType)
+ }
+ }
+
+ // MARK: - ARKit
+
+ func showTrackingQualityInfo(for trackingState: ARCamera.TrackingState, autoHide: Bool) {
+ switch trackingState {
+ case .notAvailable:
+ showMessage("TRACKING UNAVAILABLE", autoHide: true)
+ return
+ case .limited(.excessiveMotion):
+ showMessage("TRACKING LIMITED\nExcessive motion", autoHide: true)
+ return
+ case .limited(.insufficientFeatures):
+ showMessage("TRACKING LIMITED\nLow detail", autoHide: true)
+ return
+ case .limited(.initializing):
+ showMessage("Initializing", autoHide: true)
+ return
+ case .limited(.relocalizing):
+ showMessage("Recovering from session interruption", autoHide: true)
+ return
+ default:
+ return
+ }
+ }
+
+ func escalateFeedback(for trackingState: ARCamera.TrackingState, inSeconds seconds: TimeInterval) {
+ cancelScheduledMessage(for: .trackingStateEscalation)
+
+ let timer = Timer.scheduledTimer(withTimeInterval: seconds, repeats: false, block: { [unowned self] _ in
+ self.cancelScheduledMessage(for: .trackingStateEscalation)
+
+ var message = trackingState.presentationString
+ if let recommendation = trackingState.recommendation {
+ message.append(": \(recommendation)")
+ }
+
+ self.showMessage(message, autoHide: false)
+ })
+
+ timers[.trackingStateEscalation] = timer
+ }
+
+ // MARK: - IBActions
+
+ @IBAction private func restartExperience(_ sender: UIButton) {
+ restartExperienceHandler()
+ }
+
+ // MARK: - Panel Visibility
+
+ private func setMessageHidden(_ hide: Bool, animated: Bool) {
+ // The panel starts out hidden, so show it before animating opacity.
+ messagePanel.isHidden = false
+
+ guard animated else {
+ messagePanel.alpha = hide ? 0 : 1
+ return
+ }
+
+ UIView.animate(withDuration: 0.2, delay: 0, options: [.beginFromCurrentState], animations: {
+ self.messagePanel.alpha = hide ? 0 : 1
+ }, completion: nil)
+ }
+
+ private func setPauseHidden(_ hide: Bool, animated: Bool) {
+ // The panel starts out hidden, so show it before animating opacity.
+ pausePanel.isHidden = false
+
+ guard animated else {
+ pausePanel.alpha = hide ? 0 : 1
+ return
+ }
+
+ UIView.animate(withDuration: 0.2, delay: 0, options: [.beginFromCurrentState], animations: {
+ self.pausePanel.alpha = hide ? 0 : 1
+ }, completion: nil)
+ }
+}
+
+extension ARCamera.TrackingState {
+ var presentationString: String {
+ switch self {
+ case .notAvailable:
+ return "TRACKING UNAVAILABLE"
+ case .normal:
+ return "TRACKING NORMAL"
+ case .limited(.excessiveMotion):
+ return "TRACKING LIMITED\nExcessive motion"
+ case .limited(.insufficientFeatures):
+ return "TRACKING LIMITED\nLow detail"
+ case .limited(.initializing):
+ return "Initializing"
+ case .limited(.relocalizing):
+ return "Recovering from session interruption"
+ default:
+ return "TRACKING UNAVAILABLE"
+ }
+ }
+
+ var recommendation: String? {
+ switch self {
+ case .limited(.excessiveMotion):
+ return "Try slowing down your movement, or reset the session."
+ case .limited(.insufficientFeatures):
+ return "Try pointing at a flat surface, or reset the session."
+ case .limited(.relocalizing):
+ return "Try returning to where you were when the interruption began, or reset the session."
+ default:
+ return nil
+ }
+ }
+}
+
diff --git a/contrib/ar4dcops/app_ios/FlowAR/AppDelegate.swift b/contrib/ar4dcops/app_ios/FlowAR/AppDelegate.swift
new file mode 100644
index 000000000..e2d47e74f
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/AppDelegate.swift
@@ -0,0 +1,45 @@
+//
+// AppDelegate.swift
+// FlowAR
+//
+// Created by 周舒意 on 2020/10/20.
+// Copyright © 2020 周舒意. All rights reserved.
+//
+
+import UIKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ // Override point for customization after application launch.
+ return true
+ }
+
+ func applicationWillResignActive(_ application: UIApplication) {
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
+ }
+
+ func applicationDidEnterBackground(_ application: UIApplication) {
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+ }
+
+ func applicationWillEnterForeground(_ application: UIApplication) {
+ // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
+ }
+
+ func applicationDidBecomeActive(_ application: UIApplication) {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+ }
+
+ func applicationWillTerminate(_ application: UIApplication) {
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+ }
+
+
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Contents.json
new file mode 100644
index 000000000..a6cee2fed
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Contents.json
@@ -0,0 +1,17 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "resources" : [
+ {
+ "filename" : "Scan Nov 11, 2020 at 15.03.arreferenceimage"
+ },
+ {
+ "filename" : "Scan Nov 15, 2020 at 16.17 page 1.arreferenceimage"
+ },
+ {
+ "filename" : "Scan Nov 25, 2020 at 10.52.arreferenceimage"
+ }
+ ]
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Dec 9, 2020 at 13.17.arreferenceimage/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Dec 9, 2020 at 13.17.arreferenceimage/Contents.json
new file mode 100644
index 000000000..005e8f8cd
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Dec 9, 2020 at 13.17.arreferenceimage/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "Scan Dec 9, 2020 at 13.17.jpg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "width" : 1.1052145214521454
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Dec 9, 2020 at 13.17.arreferenceimage/Scan Dec 9, 2020 at 13.17.jpg b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Dec 9, 2020 at 13.17.arreferenceimage/Scan Dec 9, 2020 at 13.17.jpg
new file mode 100644
index 000000000..7080d1b25
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Dec 9, 2020 at 13.17.arreferenceimage/Scan Dec 9, 2020 at 13.17.jpg differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 11, 2020 at 15.03.arreferenceimage/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 11, 2020 at 15.03.arreferenceimage/Contents.json
new file mode 100644
index 000000000..4369da9bf
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 11, 2020 at 15.03.arreferenceimage/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "Scan Nov 11, 2020 at 15.03.jpg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "unit" : "centimeters",
+ "width" : 7
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 11, 2020 at 15.03.arreferenceimage/Scan Nov 11, 2020 at 15.03.jpg b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 11, 2020 at 15.03.arreferenceimage/Scan Nov 11, 2020 at 15.03.jpg
new file mode 100644
index 000000000..5c3b0e3f6
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 11, 2020 at 15.03.arreferenceimage/Scan Nov 11, 2020 at 15.03.jpg differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 15, 2020 at 16.17 page 1.arreferenceimage/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 15, 2020 at 16.17 page 1.arreferenceimage/Contents.json
new file mode 100644
index 000000000..dafc00e0a
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 15, 2020 at 16.17 page 1.arreferenceimage/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "Scan Nov 15, 2020 at 16.17 page 1.jpg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "unit" : "centimeters",
+ "width" : 19
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 15, 2020 at 16.17 page 1.arreferenceimage/Scan Nov 15, 2020 at 16.17 page 1.jpg b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 15, 2020 at 16.17 page 1.arreferenceimage/Scan Nov 15, 2020 at 16.17 page 1.jpg
new file mode 100644
index 000000000..558edd50e
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 15, 2020 at 16.17 page 1.arreferenceimage/Scan Nov 15, 2020 at 16.17 page 1.jpg differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 25, 2020 at 10.52.arreferenceimage/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 25, 2020 at 10.52.arreferenceimage/Contents.json
new file mode 100644
index 000000000..7d2fdc66a
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 25, 2020 at 10.52.arreferenceimage/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "Scan Nov 25, 2020 at 10.52.jpg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "width" : 0.47999999999999998
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 25, 2020 at 10.52.arreferenceimage/Scan Nov 25, 2020 at 10.52.jpg b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 25, 2020 at 10.52.arreferenceimage/Scan Nov 25, 2020 at 10.52.jpg
new file mode 100644
index 000000000..cacfa7dde
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AR Resources.arresourcegroup/Scan Nov 25, 2020 at 10.52.arreferenceimage/Scan Nov 25, 2020 at 10.52.jpg differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon.png
new file mode 100644
index 000000000..0f68ad24d
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_120-1.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_120-1.png
new file mode 100644
index 000000000..559d57e9b
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_120-1.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_120.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_120.png
new file mode 100644
index 000000000..559d57e9b
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_120.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_152.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_152.png
new file mode 100644
index 000000000..87d8196f0
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_152.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_167.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_167.png
new file mode 100644
index 000000000..8849feb41
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_167.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_180.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_180.png
new file mode 100644
index 000000000..90bde9bfc
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_180.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_20.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_20.png
new file mode 100644
index 000000000..6da522f3c
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_20.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_29.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_29.png
new file mode 100644
index 000000000..7d0942922
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_29.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40-1.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40-1.png
new file mode 100644
index 000000000..a8bc8e1ba
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40-1.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40-2.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40-2.png
new file mode 100644
index 000000000..a8bc8e1ba
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40-2.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40.png
new file mode 100644
index 000000000..a8bc8e1ba
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_40.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_58-1.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_58-1.png
new file mode 100644
index 000000000..693446347
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_58-1.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_58.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_58.png
new file mode 100644
index 000000000..693446347
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_58.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_60.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_60.png
new file mode 100644
index 000000000..ccc10637a
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_60.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_76.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_76.png
new file mode 100644
index 000000000..27a08cfee
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_76.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_80-1.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_80-1.png
new file mode 100644
index 000000000..84fd53dee
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_80-1.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_80.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_80.png
new file mode 100644
index 000000000..84fd53dee
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_80.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_87.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_87.png
new file mode 100644
index 000000000..7242d8972
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/ARdc_icon_87.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..609fa1dab
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,116 @@
+{
+ "images" : [
+ {
+ "filename" : "ARdc_icon_40-2.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "ARdc_icon_60.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "ARdc_icon_58-1.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "ARdc_icon_87.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "ARdc_icon_80-1.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "ARdc_icon_120.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "ARdc_icon_120-1.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "ARdc_icon_180.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "ARdc_icon_20.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "ARdc_icon_40-1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "ARdc_icon_29.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "ARdc_icon_58.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "ARdc_icon_40.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "ARdc_icon_80.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "ARdc_icon_76.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "ARdc_icon_152.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "ARdc_icon_167.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "ARdc_icon.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/Contents.json
new file mode 100644
index 000000000..c63d97c35
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "refresh@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "refresh@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/refresh@2x.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/refresh@2x.png
new file mode 100644
index 000000000..365ed4913
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/refresh@2x.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/refresh@3x.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/refresh@3x.png
new file mode 100644
index 000000000..4bea81fe9
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restart.imageset/refresh@3x.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/Contents.json
new file mode 100644
index 000000000..8760843e6
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "refreshPressed@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "refreshPressed@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/refreshPressed@2x.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/refreshPressed@2x.png
new file mode 100644
index 000000000..8067d4234
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/refreshPressed@2x.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/refreshPressed@3x.png b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/refreshPressed@3x.png
new file mode 100644
index 000000000..9475b1487
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Assets.xcassets/restartPressed.imageset/refreshPressed@3x.png differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Base.lproj/LaunchScreen.storyboard b/contrib/ar4dcops/app_ios/FlowAR/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 000000000..865e9329f
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Base.lproj/Main.storyboard b/contrib/ar4dcops/app_ios/FlowAR/Base.lproj/Main.storyboard
new file mode 100644
index 000000000..154baeba2
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Base.lproj/Main.storyboard
@@ -0,0 +1,228 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/Library/ProjectLibrary/Contents.json b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/Library/ProjectLibrary/Contents.json
new file mode 100644
index 000000000..c44dc44f3
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/Library/ProjectLibrary/Contents.json
@@ -0,0 +1,3 @@
+[
+
+]
\ No newline at end of file
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/Library/ProjectLibrary/Version.json b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/Library/ProjectLibrary/Version.json
new file mode 100644
index 000000000..ea86f98e8
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/Library/ProjectLibrary/Version.json
@@ -0,0 +1,4 @@
+{
+ "LibraryID" : "BBB5EDD2-CBE1-49E5-931D-2C81F724D19D",
+ "Version" : "1.0"
+}
\ No newline at end of file
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3 b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3
new file mode 100644
index 000000000..69378c280
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3 differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3.thumbnails/square b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3.thumbnails/square
new file mode 100644
index 000000000..a69f880f4
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3.thumbnails/square differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3.thumbnails/wide b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3.thumbnails/wide
new file mode 100644
index 000000000..51516d229
Binary files /dev/null and b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/SceneThumbnails/F9610871-0955-494F-A5C3-51D1A281BAB3.thumbnails/wide differ
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/com.apple.RCFoundation.Project b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/com.apple.RCFoundation.Project
new file mode 100644
index 000000000..b47d290c9
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Experience.rcproject/com.apple.RCFoundation.Project
@@ -0,0 +1,155 @@
+{
+ "__version" : 1,
+ "__content" : [
+ {
+ "tags" : {
+ "__version" : 1,
+ "__content" : [
+ {
+ "tagsStore" : {
+
+ }
+ }
+ ]
+ },
+ "scenes" : [
+ {
+ "__version" : 2,
+ "__content" : [
+ {
+ "hasGroundPlane" : true,
+ "gravity" : [
+ 0,
+ -9.8000001907348633,
+ 0
+ ],
+ "arAnchorSpecification" : {
+ "name" : "surface",
+ "classification" : 0,
+ "alignment" : 1
+ },
+ "title" : "Box",
+ "material" : "concrete",
+ "layoutData" : {
+ "version" : 1,
+ "constraints" : [
+
+ ]
+ },
+ "identifier" : "F9610871-0955-494F-A5C3-51D1A281BAB3",
+ "behaviors" : [
+
+ ],
+ "overrides" : {
+ "factory" : null,
+ "children" : {
+ "4A213441-135C-450E-8EF8-1A4EAB267C1D" : {
+ "overrides" : {
+ "factory" : {
+ "version" : "1.0",
+ "identifier" : "com.apple.rc.af.CoreAssetFactories.PrimitiveShapeAssetFactory"
+ },
+ "arguments" : [
+ [
+ "material",
+ {
+ "value" : "steel",
+ "typeName" : "String"
+ }
+ ],
+ [
+ "type",
+ {
+ "value" : "box",
+ "typeName" : "String"
+ }
+ ]
+ ],
+ "runtimeAttributes" : [
+ [
+ "RuntimeIdentifier",
+ {
+ "value" : "3788A5B5-0388-488A-8304-7B90FEBA6FAC",
+ "typeName" : "UUID"
+ }
+ ],
+ [
+ "entityName",
+ {
+ "value" : "Steel Box",
+ "typeName" : "String"
+ }
+ ]
+ ]
+ },
+ "transform" : {
+ "matrix" : [
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ 0.05000000074505806,
+ 0,
+ 1
+ ]
+ }
+ },
+ "B91C7CA3-4AB1-4253-9A84-07383168A8A8" : null,
+ "7F5F1ADC-E5E4-4EF5-A5B5-FC43B071FFA5" : null,
+ "632E476F-1E55-4E30-9723-1C51DD2E8B89" : null
+ }
+ },
+ "viewTransform" : {
+ "sceneTransform" : [
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.99999994039535522,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.99999994039535522,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "cameraTransform" : [
+ 0.99999994039535522,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0.80754852294921875,
+ -0.58980101346969604,
+ 0,
+ 0,
+ 0.58980101346969604,
+ 0.80754852294921875,
+ 0,
+ 0,
+ 1.135667085647583,
+ 1.5549418926239014,
+ 1
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/contrib/ar4dcops/app_ios/FlowAR/FlowgateClient.swift b/contrib/ar4dcops/app_ios/FlowAR/FlowgateClient.swift
new file mode 100644
index 000000000..5bf33dfe1
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/FlowgateClient.swift
@@ -0,0 +1,232 @@
+//
+// FlowgateClient.swift
+// FlowAR
+//
+// Created by 周舒意 on 2020/11/8.
+// Copyright © 2020 周舒意. All rights reserved.
+//
+
+import Foundation
+import UIKit
+import ARKit
+extension ViewController{
+
+ func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
+ completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
+
+ }
+
+
+ func getFlowgateToken() -> [String: Any]{
+ if (!self.current_token.isEmpty){
+ let current_time = Int(round(Date().timeIntervalSince1970 * 1000))
+ guard let expire_time = self.current_token["expires_in"]! as? Int else {return ["fail token":0]}
+ if (expire_time - current_time > 600000){
+ return self.current_token
+ }
+ }
+
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+ let token_url = URL(string: self.host + "/apiservice/v1/auth/token")
+ var request = URLRequest(url: token_url!)
+ // header
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+ // body dump to json
+ if let theJSONData = try? JSONSerialization.data(
+ withJSONObject: ["userName": self.username, "password": self.password],
+ options: []) {
+ request.httpBody = theJSONData
+ request.httpMethod = "POST"
+ let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
+ if (error != nil){print(error.debugDescription)}
+ guard let data = data, error == nil else {
+ print(error?.localizedDescription ?? "get nothing")
+ return
+ }
+ if let httpResponse = response as? HTTPURLResponse{
+ if httpResponse.statusCode==200{
+ do {
+ self.current_token = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
+ }catch _ {
+ print("JSONSerialization error:", error as Any)
+ }
+ }
+ }
+ })
+ task.resume()
+ }
+ return current_token
+ }
+
+ func getAssetByName(name: String) {
+ let _token = self.getFlowgateToken()
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+ let token_url = URL(string: self.host + "/apiservice/v1/assets/name/" + name + "/")
+ var request = URLRequest(url: token_url!)
+ // header
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+ guard let acc_token = _token["access_token"] as? String else {
+ return
+ }
+ print(acc_token)
+ request.addValue(("Bearer " + acc_token), forHTTPHeaderField: "Authorization")
+ request.httpMethod = "GET"
+ print("getAssetByName")
+ let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
+ if (error != nil){print(error.debugDescription)}
+ print(2)
+ guard let data = data, error == nil else {
+ print(error?.localizedDescription ?? "get nothing")
+ return
+ }
+ if let httpResponse = response as? HTTPURLResponse{
+ if httpResponse.statusCode==200{
+ do{
+ self.fetch_result = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
+ self.cabinet_b = true
+ }catch _{ print("JSONSerialization error:", error as Any)}
+ }
+ }
+ })
+ task.resume()
+ }
+
+ func getAssetByID(ID:String){
+// DispatchQueue.main.async {
+// self.statusViewController.cancelAllScheduledMessages()
+// self.statusViewController.showMessage("getAssetByID")
+// }
+ print("getAssetById")
+ let _token = self.getFlowgateToken()
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+ let token_url = URL(string: self.host + "/apiservice/v1/assets/" + ID + "/")
+ var request = URLRequest(url: token_url!)
+ // header
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+ guard let acc_token = _token["access_token"] as? String else {
+ return
+ }
+ request.addValue(("Bearer " + acc_token), forHTTPHeaderField: "Authorization")
+ request.httpMethod = "GET"
+ let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
+ if (error != nil){print(error.debugDescription)}
+ print(2)
+ guard let data = data, error == nil else {
+ print(error?.localizedDescription ?? "get nothing")
+ return
+ }
+ if let httpResponse = response as? HTTPURLResponse{
+ if httpResponse.statusCode==200{
+ do{
+// DispatchQueue.main.async {
+// self.statusViewController.cancelAllScheduledMessages()
+// self.statusViewController.showMessage("Internet")
+// }
+ let info = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
+ self.detectedDataResult[ID] = info
+ print("getAssetByIdonline")
+ self.sceneView.session.add(anchor: self.detectedDataAnchor[ID]!!)
+
+ }catch _{ print("JSONSerialization error:", error as Any)}
+ }
+ }
+ })
+ task.resume()
+ }
+
+ func getAssetByIDNAnchor(ID: String){
+
+ let _token = self.getFlowgateToken()
+ let config = URLSessionConfiguration.default
+ let session = URLSession(configuration: config, delegate: self, delegateQueue: OperationQueue.main)
+ let token_url = URL(string: self.host + "/apiservice/v1/assets/" + ID + "/")
+ var request = URLRequest(url: token_url!)
+ // header
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+ guard let acc_token = _token["access_token"] as? String else {
+ return
+ }
+ request.addValue(("Bearer " + acc_token), forHTTPHeaderField: "Authorization")
+ request.httpMethod = "GET"
+ let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
+ if (error != nil){print(error.debugDescription)}
+ print(2)
+ guard let data = data, error == nil else {
+ print(error?.localizedDescription ?? "get nothing")
+ return
+ }
+ if let httpResponse = response as? HTTPURLResponse{
+ if httpResponse.statusCode==200{
+ do{
+ let info = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
+ self.detectedDataResult[ID] = info
+// self.lastAddedAnchor = self.detectedDataAnchor[ID] as? ARAnchor
+
+ self.statusViewController.showPause()
+ self.statusViewController.unhidePause()
+ self.cabinet = info?["cabinetName"] as? String
+ DispatchQueue.main.async {
+ guard let _ = self.cabinet else {
+ return
+ }}
+ DispatchQueue.main.async {
+ self.statusViewController.cancelAllScheduledMessages()
+ self.statusViewController.showMessage("Work around to detect the rack" + self.cabinet)
+ }
+ guard let _ = self.cabinet else {
+ self.statusViewController.cancelAllScheduledMessages()
+ self.statusViewController.showMessage("Not in cabinet")
+ return
+ }
+ print("getAssetByIDNAnchor")
+ self.getAssetByName(name: self.cabinet)
+
+ }catch _{ print("JSONSerialization error:", error as Any)}
+ }
+ }
+ })
+ task.resume()
+ }
+
+ func strFormat(content: [String: Any]) -> [String: String]{
+// let content = self.detectedDataResult[ID]! as [String: Any]
+ var result: [String: String] = ["type":"", "content":"", "title":""]
+ for item in items{
+ if item == "assetName" {
+ result["title"] = content[item] as? String
+ continue
+ }
+ if item.contains("."){
+ let item_l = item.split(separator: ".") // "xxx.aa/bb" get [xx, aa/bb]
+ let left = String(item_l[0]) // left = xx
+ guard let temp_o = content[left] as? [String: Any] else {return ["error":"no a list"]} // temp_o = content[xx]
+ let item_i = item_l[1].split(separator: "/") // [aa, bb]
+ result["type"]! += left + "\n"
+ result["content"]! += "\n"
+ for item_in in item_i{ // aa
+ result["type"]! += "\t" + item_in + "\n"
+ if let temp = temp_o[String(item_in)] as? String {
+ result["content"]! += temp + "\n"
+ } else if let temp = temp_o[String(item_in)] as? Int {
+ result["content"]! += String(format: "%d", temp) + "\n"
+ }
+ }
+ }
+ else {
+ if let temp = content[item] as? String {
+ result["type"]! += item + "\n"
+ result["content"]! += temp + "\n"
+ } else if let temp = content[item] as? Int {
+ result["type"]! += item + "\n"
+ result["content"]! += String(format: "%d", temp) + "\n"
+ }
+ }
+ }
+ return result
+ }
+
+
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/Info.plist b/contrib/ar4dcops/app_ios/FlowAR/Info.plist
new file mode 100644
index 000000000..d3b409d30
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/Info.plist
@@ -0,0 +1,63 @@
+
+
+
+
+ NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ DatA CenteR
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSRequiresIPhoneOS
+
+ NSCameraUsageDescription
+ The camera is used for augmenting reality
+ NSLocationWhenInUseUsageDescription
+ The location is used for augmenting reality
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIRequiredDeviceCapabilities
+
+ armv7
+ arkit
+
+ UIRequiresFullScreen
+
+ UIStatusBarHidden
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+
+
diff --git a/contrib/ar4dcops/app_ios/FlowAR/RoundedButton.swift b/contrib/ar4dcops/app_ios/FlowAR/RoundedButton.swift
new file mode 100644
index 000000000..b7f93a13f
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/RoundedButton.swift
@@ -0,0 +1,36 @@
+/*
+See LICENSE folder for this sample’s licensing information.
+
+Abstract:
+A custom button that stands out over the camera view.
+*/
+
+import UIKit
+
+@IBDesignable
+class RoundedButton: UIButton {
+
+ override init(frame: CGRect) {
+ super.init(frame: frame)
+ setup()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ setup()
+ }
+
+ func setup() {
+ backgroundColor = tintColor
+ layer.cornerRadius = 8
+ clipsToBounds = true
+ setTitleColor(.white, for: [])
+ titleLabel?.font = UIFont.boldSystemFont(ofSize: 17)
+ }
+
+ override var isEnabled: Bool {
+ didSet {
+ backgroundColor = isEnabled ? tintColor : .gray
+ }
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/StartViewController.swift b/contrib/ar4dcops/app_ios/FlowAR/StartViewController.swift
new file mode 100644
index 000000000..769c1e1d7
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/StartViewController.swift
@@ -0,0 +1,15 @@
+//
+// StartViewController.swift
+// FlowAR
+//
+// Created by 周舒意 on 2020/11/11.
+// Copyright © 2020 周舒意. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+class StartViewController: UIViewController{
+
+
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/ViewController+ARSessionDelegate.swift b/contrib/ar4dcops/app_ios/FlowAR/ViewController+ARSessionDelegate.swift
new file mode 100644
index 000000000..7d3b0a548
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/ViewController+ARSessionDelegate.swift
@@ -0,0 +1,96 @@
+//
+// ViewController+ARSessionDelegate.swift
+// FlowAR
+//
+// Created by 周舒意 on 2020/11/11.
+// Copyright © 2020 周舒意. All rights reserved.
+//
+
+import ARKit
+
+extension ViewController {
+
+ // MARK: - ARSessionDelegate
+
+ func session(_ session: ARSession, cameraDidChangeTrackingState camera: ARCamera) {
+ statusViewController.showTrackingQualityInfo(for: camera.trackingState, autoHide: true)
+
+ switch camera.trackingState {
+ case .notAvailable, .limited:
+ statusViewController.escalateFeedback(for: camera.trackingState, inSeconds: 3.0)
+ case .normal:
+ statusViewController.cancelScheduledMessage(for: .trackingStateEscalation)
+ }
+ }
+
+ func session(_ session: ARSession, didFailWithError error: Error) {
+ guard error is ARError else { return }
+
+ let errorWithInfo = error as NSError
+ let messages = [
+ errorWithInfo.localizedDescription,
+ errorWithInfo.localizedFailureReason,
+ errorWithInfo.localizedRecoverySuggestion
+ ]
+
+ // Use `flatMap(_:)` to remove optional error messages.
+ let errorMessage = messages.compactMap({ $0 }).joined(separator: "\n")
+
+ DispatchQueue.main.async {
+ self.displayErrorMessage(title: "The AR session failed.", message: errorMessage)
+ }
+ }
+
+ func sessionWasInterrupted(_ session: ARSession) {
+ blurView.isHidden = false
+ statusViewController.showMessage("""
+ SESSION INTERRUPTED
+ The session will be reset after the interruption has ended.
+ """, autoHide: false)
+ }
+
+ func sessionInterruptionEnded(_ session: ARSession) {
+ blurView.isHidden = true
+ statusViewController.showMessage("RESETTING SESSION")
+
+ restartExperience()
+ }
+
+ func sessionShouldAttemptRelocalization(_ session: ARSession) -> Bool {
+ return true
+ }
+
+ // MARK: - Error handling
+
+ func displayErrorMessage(title: String, message: String) {
+ // Blur the background.
+ blurView.isHidden = false
+
+ // Present an alert informing about the error that has occurred.
+ let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
+ let restartAction = UIAlertAction(title: "Restart Session", style: .default) { _ in
+ alertController.dismiss(animated: true, completion: nil)
+ self.blurView.isHidden = true
+ self.resetTracking()
+ }
+ alertController.addAction(restartAction)
+ present(alertController, animated: true, completion: nil)
+ }
+
+ // MARK: - Interface Actions
+
+ func restartExperience() {
+ guard isRestartAvailable else { return }
+ isRestartAvailable = false
+
+ statusViewController.cancelAllScheduledMessages()
+
+ resetTracking()
+
+ // Disable restart for a while in order to give the session time to restart.
+ DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
+ self.isRestartAvailable = true
+ }
+ }
+
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/ViewController.swift b/contrib/ar4dcops/app_ios/FlowAR/ViewController.swift
new file mode 100644
index 000000000..337bd659c
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/ViewController.swift
@@ -0,0 +1,632 @@
+//
+// ViewController.swift
+// FlowAR
+//
+// Created by 周舒意 on 2020/10/20.
+// Copyright © 2020 周舒意. All rights reserved.
+//
+
+import UIKit
+import SceneKit
+import ARKit
+import Vision
+import ClassKit
+
+class ViewController: UIViewController, ARSCNViewDelegate, ARSessionDelegate, URLSessionDelegate {
+
+ @IBOutlet var sceneView: ARSCNView!
+
+ @IBOutlet weak var blurView: UIVisualEffectView!
+
+ var temperatures = [25.5, 25.8, 28, 26.9]
+
+ var qrRequests = [VNRequest]()
+ var detectedDataAnchor: [String: ARAnchor?] = [:] // QR location
+ var message: String! // QR Message === ID
+ var detectedDataResult: [String: [String: Any]] = [:] // [ID: ["AssetId":"sfsdf", "AssetName": "sfdsfd"]]
+ var cabinet: String! // cabinet name -> getAssetByName
+ var cabitnetNode: SCNNode?
+
+ var paused = false
+
+ var chartNode: SCNNode?
+ var addPlane: SCNNode?
+
+ /// The view controller that displays the status and "restart experience" UI.
+ lazy var statusViewController: StatusViewController = {
+ return children.lazy.compactMap({ $0 as? StatusViewController }).first!
+ }()
+ /// A serial queue for thread safety when modifying the SceneKit node graph.
+ let updateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! +
+ ".serialSceneKitQueue")
+
+ /// Convenience accessor for the session owneASd by ARSCNView.
+ var session: ARSession {
+ return sceneView.session
+ }
+
+ var lastAddedAnchor: ARAnchor? // 最近添加的QRcode的anchor
+ var processing = false // QR code image process
+
+
+ let host = "https://202.121.180.32/" // FLOWGATE_HOST
+ var password = "QWxv_3arJ70gl" // FLOWGATE_PASSWORD
+ var username = "API"
+ var current_token: [String: Any] = [:]
+ let items = [
+ "assetName",
+ "assetNumber",
+ "assetSource",
+ "category",
+ "subCategory",
+ "manufacturer",
+ "model",
+ "tag",
+ "cabinetName"] // 看情况加
+ var fetch_result: [String: Any] = [:] // data for one cabinet
+
+ lazy var cabinet_b = false {// is cabinet detected
+ didSet{
+ if(cabinet_b) {self.startFigure()}
+ }
+ }
+ var cabinet_show = false;
+ // MARK: - View Controller Life Cycle
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ Swift.print(getFlowgateToken())
+ // Set the view's delegate
+ sceneView.delegate = self
+ sceneView.session.delegate=self
+// sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin]
+
+ // Hook up status view controller callback(s).
+ statusViewController.restartExperienceHandler = { [unowned self] in
+ self.restartExperience()
+ }
+ startQrCodeDetection()
+ statusViewController.pauseSessionHandler = {[unowned self] in
+ if(paused){
+ self.paused = false
+ let configuration = ARWorldTrackingConfiguration()
+ session.run(configuration, options: [])
+ statusViewController.showPause()
+ }else{
+ self.paused = true
+ session.pause()
+ statusViewController.showContinue()
+ }}
+
+ }
+// override func viewWillAppear(_ animated: Bool) {
+// super.viewWillAppear(animated)
+// print("heihei")
+//
+// }
+ override func viewDidAppear(_ animated: Bool) {
+ super.viewDidAppear(animated)
+
+ // Prevent the screen from being dimmed to avoid interuppting the AR experience.
+ UIApplication.shared.isIdleTimerDisabled = true
+
+ // Start the AR experience
+ resetTracking()
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ super.viewWillDisappear(animated)
+
+ session.pause()
+ }
+
+ // MARK: - Session management (Image detection setup)
+
+ /// Prevents restarting the session while a restart is in progress.
+ var isRestartAvailable = true
+
+ /// Creates a new AR configuration to run on the `session`.
+ /// - Tag: ARReferenceImage-Loading
+ func startFigure(){
+ guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
+ fatalError("Missing expected asset catalog resources.")
+ }
+ let configuration = ARWorldTrackingConfiguration()
+ configuration.detectionImages = referenceImages
+ session.run(configuration)
+ }
+
+ func stopFigure(){
+ let configuration = ARWorldTrackingConfiguration()
+ session.run(configuration)
+ }
+ func resetTracking() {
+
+// guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
+// fatalError("Missing expected asset catalog resources.")
+// }
+//
+ statusViewController.hidePause()
+ statusViewController.showPause()
+ let configuration = ARWorldTrackingConfiguration()
+ session.run(configuration, options: [.resetTracking, .removeExistingAnchors])
+
+ statusViewController.scheduleMessage("Look around to detect server", inSeconds: 7.5, messageType: .contentPlacement)
+ cabinet_b = false;
+ cabinet_show = false;
+ }
+
+
+ func startQrCodeDetection() {
+
+ // Create a Barcode Detection Request
+ let request = VNDetectBarcodesRequest(completionHandler: self.requestHandler)
+ // Set it to recognize QR code only
+ request.symbologies = [.Aztec, .Code39, .Code39Checksum, .Code39FullASCII, .Code39FullASCIIChecksum, .Code93, .Code93i, .Code128, .DataMatrix, .EAN8,
+ .EAN13, .I2of5, .I2of5Checksum, .ITF14, .PDF417, .UPCE]
+ self.qrRequests = [request]
+ }
+
+ func requestHandler(request: VNRequest, error: Error?) {
+ // Get the first result out of the results, if there are any
+ if let results = request.results, let result = results.first as? VNBarcodeObservation {
+ guard let message=result.payloadStringValue else {return}
+ self.message = message
+ Swift.print(self.message ?? "No message.")
+ DispatchQueue.main.async {
+ self.statusViewController.cancelAllScheduledMessages()
+ self.statusViewController.showMessage("Detected a bar code")
+ }
+ // Get the bounding box for the bar code and find the center
+ var rect = result.boundingBox
+ // TODO: Draw it
+ // Flip coordinates
+ rect = rect.applying(CGAffineTransform(scaleX: 1, y: -1))
+ rect = rect.applying(CGAffineTransform(translationX: 0, y: 1))
+ // Get center
+ let center = CGPoint(x: rect.midX, y: rect.midY)
+ if(cabinet_show)
+ {
+ DispatchQueue.main.async {
+ self.statusViewController.cancelAllScheduledMessages()
+ self.statusViewController.showMessage("Loading information")
+ }
+ DispatchQueue.main.async {
+ self.hitTestQrCode(center: center, message: message)
+ self.processing = false
+ }
+ }else{
+ getAssetByIDNAnchor(ID: message)
+ self.hitTestQrCodeFirst(center: center, message: message)
+ self.processing = false
+ }
+ } else {
+ self.processing = false
+ }
+ }
+
+ func hitTestQrCode(center: CGPoint, message: String) {
+// print(detectedDataResult)
+ if let hitTestResults = sceneView?.hitTest(center, types: [.featurePoint] ),
+ let hitTestResult = hitTestResults.first {
+ if let detectedDataAnchor = self.detectedDataAnchor[message], // ID的anchor
+ let node = self.sceneView.node(for: detectedDataAnchor!) {
+ node.transform = SCNMatrix4(hitTestResult.worldTransform)
+ node.rotation = self.cabitnetNode?.rotation ?? node.rotation
+ } else {
+ // Create an anchor. The node will be created in delegate methods
+// self.detectedDataAnchor[message] = hitTestResult.anchor
+ self.detectedDataAnchor[message] = ARAnchor(transform: hitTestResult.worldTransform)
+ self.lastAddedAnchor = self.detectedDataAnchor[message] as? ARAnchor
+ self.getAssetByID(ID: message)
+ }
+ }
+ }
+
+ func hitTestQrCodeFirst(center: CGPoint, message: String) {
+// print(detectedDataResult)
+ if let hitTestResults = sceneView?.hitTest(center, types: [.featurePoint] ),
+ let hitTestResult = hitTestResults.first {
+ // Create an anchor. The node will be created in delegate methods
+// self.detectedDataAnchor[message] = hitTestResult.anchor
+ self.detectedDataAnchor[message] = ARAnchor(transform: hitTestResult.worldTransform)
+ self.lastAddedAnchor = self.detectedDataAnchor[message] as? ARAnchor
+ }
+ }
+
+ public func session(_ session: ARSession, didUpdate frame: ARFrame) {
+ DispatchQueue.global(qos: .userInitiated).async {
+ do {
+ if self.processing {
+ return
+ }
+ self.processing = true
+ // Create a request handler using the captured image from the ARFrame
+ let imageRequestHandler = VNImageRequestHandler(cvPixelBuffer: frame.capturedImage,
+ options: [:])
+ // Process the request
+ try imageRequestHandler.perform(self.qrRequests)
+ } catch {
+
+ }
+ }
+ }
+
+ // Anchor(定位) ->Node(Anchor) object -> node加进你要的返回的node
+ // Geometry _^
+
+ // MARK: - ARSCNViewDelegate
+
+
+ // Override to create and configure nodes for anchors added to the view's session.
+ func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
+ if self.lastAddedAnchor?.identifier == anchor.identifier {
+
+// DispatchQueue.main.async {
+// self.statusViewController.cancelAllScheduledMessages()
+// self.statusViewController.showMessage("add this anchor")
+// }
+ let node = SCNNode()
+ guard let ID = self.message else { return node }
+ let result = strFormat(content: detectedDataResult[ID]! as [String: Any])
+ let left_message = textNode(text: result["type"]!, position: SCNVector3(-0.11, 0.05, 0.01))
+ let right_message = textNode(text: result["content"]!, position: SCNVector3(0, 0.05, 0.01))
+ let title_message = textNode(text: result["title"]!, position: SCNVector3(-0.11, 0.09, 0.01), bold: true, size: 2)
+ left_message.eulerAngles.x = 0
+ right_message.eulerAngles.x = 0
+ title_message.eulerAngles.x = 0
+
+ left_message.opacity = 0
+ right_message.opacity = 0
+ title_message.opacity = 0
+ node.addChildNode(left_message)
+ node.addChildNode(right_message)
+ node.addChildNode(title_message)
+
+
+ let plane = SCNPlane(width: 0.25, height: 0.2)
+ let planeNode = SCNNode(geometry: plane)
+ planeNode.eulerAngles.x = 0
+ planeNode.opacity = 0 // for fadein
+ node.addChildNode(planeNode)
+
+ let underLine = SCNPlane(width: 0.25, height: 0.002)
+ underLine.firstMaterial?.diffuse.contents = UIColor.cyan
+ let underLineNode = SCNNode(geometry: underLine)
+ underLineNode.eulerAngles.x = 0
+ underLineNode.opacity = 0 // for fadein
+ underLineNode.position = SCNVector3Make(0, 0.06, 0.001)
+ node.addChildNode(underLineNode)
+
+ planeNode.scale = SCNVector3Zero
+ planeNode.runAction(self.imageAppearAction)
+ underLineNode.scale = SCNVector3Zero
+ underLineNode.runAction(self.imageAppearAction)
+
+
+ title_message.runAction(self.textAppearAction)
+ left_message.runAction(self.textAppearAction)
+ right_message.runAction(self.textAppearAction)
+
+
+
+ return node
+
+ } else if let imageAnchor = anchor as? ARImageAnchor{
+ let referenceImage = imageAnchor.referenceImage
+ let node = SCNNode()
+ updateQueue.async {
+ guard let path = Bundle.main.path(forResource: "wireframe_shader", ofType: "metal", inDirectory: "art.scnassets"),
+ let shader = try? String(contentsOfFile: path, encoding: .utf8) else {
+ print(Bundle.main.path(forResource: "wireframe_shader", ofType: "metal", inDirectory: "Assets.xcassets") ?? "nothing")
+ print("faile to open")
+ return
+ }
+ // Create a plane to visualize the initial position of the detected image.
+ let wireFrame = SCNNode()
+ let box = SCNBox(width: referenceImage.physicalSize.width, height: 0, length: referenceImage.physicalSize.height, chamferRadius: 0)
+ box.firstMaterial?.diffuse.contents = UIColor.red
+ box.firstMaterial?.isDoubleSided = true
+ box.firstMaterial?.shaderModifiers = [.surface: shader]
+ wireFrame.geometry = box
+ node.addChildNode(wireFrame)
+ self.cabitnetNode = wireFrame
+
+ // add lines
+
+ var start_y = -referenceImage.physicalSize.height/2
+ let end_y = referenceImage.physicalSize.height/2 - (referenceImage.physicalSize.height/42) * 3
+ let part = (referenceImage.physicalSize.height/42) * 3 // every 3 units
+ let text_pos = part/2
+ var iter = 40
+
+
+ while(start_y SCNNode {
+ let mesages = SCNText(string: text, extrusionDepth: 0) // SCN开头的geometry
+ if(bold) {mesages.font = UIFont(name:"HelveticaNeue-Bold", size: 12)}
+ else {mesages.font = UIFont(name:"HelveticaNeue", size: 12)}
+ let material = SCNMaterial()
+ material.diffuse.contents = color
+ mesages.materials = [material]
+
+ let messageNode = SCNNode(geometry: mesages)
+ messageNode.scale = SCNVector3Make( 0.001*size, 0.001*size, 0.001*size)
+ // 锚点
+ // set pivot of left top point
+ var minVec = SCNVector3Zero
+ var maxVec = SCNVector3Zero
+ (minVec, maxVec) = messageNode.boundingBox
+ if(center){
+ messageNode.pivot = SCNMatrix4MakeTranslation(
+ (minVec.x + maxVec.x)/2,
+ maxVec.y,
+ minVec.z
+ )
+ messageNode.position = messageNode.position + position
+ }
+ else {messageNode.pivot = SCNMatrix4MakeTranslation(
+ minVec.x,
+ maxVec.y,
+ minVec.z
+ )
+ messageNode.eulerAngles.x = -.pi/2
+ messageNode.position = messageNode.position + position
+ }
+
+ return messageNode
+ }
+
+ func lineNode(color: UIColor, height: CGFloat, position: SCNVector3, angle: Float) -> SCNNode{
+ // Add vertical line
+ let LineConnect = SCNCylinder()
+ LineConnect.radius = 0.001
+ LineConnect.height = height
+ LineConnect.radialSegmentCount = 5
+ LineConnect.firstMaterial!.diffuse.contents = color
+ let LineNode = SCNNode(geometry: LineConnect)
+ LineNode.pivot = SCNMatrix4MakeTranslation(
+ 0,
+ -Float(height/2),
+ 0
+ )
+ LineNode.position = position
+ LineNode.eulerAngles.x = -.pi/2
+ LineNode.eulerAngles.y = -.pi/2+angle
+
+ return LineNode
+ }
+
+ func ballNode(color: UIColor, position: SCNVector3, radius: CGFloat) -> SCNNode{
+ let ballGeo = SCNSphere()
+ ballGeo.radius = radius
+ ballGeo.firstMaterial!.diffuse.contents = color
+ let ballNode = SCNNode(geometry: ballGeo)
+ ballNode.position = position
+ return ballNode
+ }
+
+ func planeNode(width: CGFloat, height: CGFloat, opacity: CGFloat, position: SCNVector3, conor: CGFloat = 0)->SCNNode{
+ let plane = SCNPlane(width: width, height: height)
+ plane.cornerRadius = conor
+ let planeNode = SCNNode(geometry: plane)
+ planeNode.eulerAngles.x = -.pi/2
+ planeNode.opacity = opacity
+ planeNode.position = position
+ return planeNode
+ }
+
+ func see_chart_button() -> SCNNode{
+ let node = SCNNode()
+ node.addChildNode(textNode(text: "Show Temperature Plots", position: SCNVector3(0, 0, 0), color: .brown))
+ node.addChildNode(lineNode(color: .cyan, height: 0.1/cos(.pi/2), position: SCNVector3(0, 0, 0), angle: -.pi/4))
+ node.addChildNode(lineNode(color: .cyan, height: 0.1/cos(.pi/2), position: SCNVector3(0, 0, 0), angle: .pi/4))
+ return node
+ }
+
+
+ @IBAction func tap_add_chart(_ sender: UITapGestureRecognizer) {
+ let currentTouchLocation = sender.location(in: sceneView)
+ print(currentTouchLocation)
+ guard let hitTestResultNode = self.sceneView.hitTest(currentTouchLocation, options: nil).first?.node else { return }
+ guard let name = hitTestResultNode.name else{ return }
+ print("name")
+ if(name=="temp"){
+ // TODO
+ print("here")
+ self.chartNode?.isHidden = false
+ self.addPlane?.runAction(self.imageAppearAction)
+ }
+
+ }
+
+ func add_chart() -> SCNNode{
+ let date = Date()
+ let calendar = Calendar.current
+ let hour = calendar.component(.hour, from: date)
+ let minutes = calendar.component(.minute, from: date)
+ let node = SCNNode()
+ node.addChildNode(lineNode(color: .darkGray, height: 0.1, position: SCNVector3(0, 0, 0), angle: .pi/2))
+ node.addChildNode(lineNode(color: .darkGray, height: 0.2, position: SCNVector3(0, 0, 0), angle: 0))
+ let height = temperatures.max()! - temperatures.min()!
+ let length = 0.18/Double(temperatures.count-1)
+ var now_height = 0.005
+ for i in 0...(temperatures.count-2){
+ let height = (temperatures[i+1] - temperatures[i])/height * 0.09
+ let angle = atan(height/length)
+ node.addChildNode(lineNode(color: .cyan, height: CGFloat(length/cos(angle)), position: SCNVector3(0.01 + length * Double(i), 0, -now_height), angle: Float(angle)))
+ node.addChildNode(ballNode(color: .cyan, position: SCNVector3(0.01 + length * Double(i), 0, -now_height), radius: 0.001))
+ node.addChildNode(textNode(text: String(temperatures[i]), position: SCNVector3Make(Float(0.01 + length * Double(i)), 0, -Float(now_height)), bold: false, size: 1, color: .darkGray))
+ node.addChildNode(textNode(text: String(hour) + ":" + String(minutes - temperatures.count + 1 + i), position: SCNVector3(Float(0.01 + length * Double(i)), 0, 0.01), bold: false, size: 1, color: .darkGray))
+ now_height += height
+ }
+ let i = temperatures.count - 1
+ node.addChildNode(textNode(text: String(temperatures[i]), position: SCNVector3(Float(0.01 + length * Double(i)), 0, -Float(now_height)), bold: false, size: 1, color: .darkGray))
+ node.addChildNode(textNode(text: String(hour) + ":" + String(minutes), position: SCNVector3(Float(0.01 + length * Double(i)), 0, 0.01), bold: false, size: 1, color: .darkGray))
+ node.addChildNode(textNode(text: "time: ", position: SCNVector3(-0.02, 0, 0.01), bold: false, size: 1, color: .darkGray))
+ return node
+ }
+
+}
+
+extension SCNVector3 {
+ static func + (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
+ return SCNVector3Make(left.x + right.x, left.y + right.y, left.z + right.z)
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowAR/art.scnassets/wireframe_shader.metal b/contrib/ar4dcops/app_ios/FlowAR/art.scnassets/wireframe_shader.metal
new file mode 100644
index 000000000..68ea78beb
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowAR/art.scnassets/wireframe_shader.metal
@@ -0,0 +1,49 @@
+/*
+See LICENSE folder for this sample’s licensing information.
+
+Abstract:
+SceneKit shader modifier to render bounding box edges with distance-based fade.
+*/
+#pragma transparent
+#pragma body
+
+float3 modelPosition = scn_node.modelTransform[3].xyz;
+float3 viewPosition = scn_frame.inverseViewTransform[3].xyz;
+float distance = length(modelPosition - viewPosition);
+
+float3 bBoxMin = scn_node.boundingBox[0];
+float3 bBoxMax = scn_node.boundingBox[1];
+float3 size = bBoxMax - bBoxMin;
+float bBoxDiag = length(size);
+
+////////////////////////////////////////////////////////////////
+// Compute per-pixel transparency based on distance from camera
+////////////////////////////////////////////////////////////////
+float closest = distance - bBoxDiag / 2.0;
+float furthest = distance + bBoxDiag / 2.0;
+float distFromPointOfView = length(_surface.position);
+float normalizedDistance = 1 - ((distFromPointOfView - closest) / (furthest - closest));
+_surface.transparent.a = clamp(normalizedDistance, 0.0, 1.0);
+
+////////////////////////////////////////////////////////////////
+// Render only a wireframe
+////////////////////////////////////////////////////////////////
+float lineThickness = 0.01;
+float u = _surface.diffuseTexcoord.x;
+float v = _surface.diffuseTexcoord.y;
+
+// Compute scaling of line thickness based on bounding box size
+float2 scale;
+if (abs((scn_node.inverseModelViewTransform * float4(_surface.normal, 0.0)).x) > 0.5) {
+ scale = size.zy;
+} else if (abs((scn_node.inverseModelViewTransform * float4(_surface.normal, 0.0)).y) > 0.5) {
+ scale = size.xz;
+} else {
+ scale = size.xy;
+}
+
+// Compute threshold for discarding rendering
+float2 thresh = float2(lineThickness) / scale;
+if (u > thresh[0] && u < (1.0 - thresh[0]) && v > thresh[1] && v < (1.0 - thresh[1])) {
+ discard_fragment();
+}
diff --git a/contrib/ar4dcops/app_ios/FlowARTests/FlowARTests.swift b/contrib/ar4dcops/app_ios/FlowARTests/FlowARTests.swift
new file mode 100644
index 000000000..38113271d
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowARTests/FlowARTests.swift
@@ -0,0 +1,34 @@
+//
+// FlowARTests.swift
+// FlowARTests
+//
+// Created by 周舒意 on 2020/10/20.
+// Copyright © 2020 周舒意. All rights reserved.
+//
+
+import XCTest
+@testable import FlowAR
+
+class FlowARTests: XCTestCase {
+
+ override func setUp() {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDown() {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testExample() {
+ // This is an example of a functional test case.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ }
+
+ func testPerformanceExample() {
+ // This is an example of a performance test case.
+ self.measure {
+ // Put the code you want to measure the time of here.
+ }
+ }
+
+}
diff --git a/contrib/ar4dcops/app_ios/FlowARTests/Info.plist b/contrib/ar4dcops/app_ios/FlowARTests/Info.plist
new file mode 100644
index 000000000..64d65ca49
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowARTests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/contrib/ar4dcops/app_ios/FlowARUITests/FlowARUITests.swift b/contrib/ar4dcops/app_ios/FlowARUITests/FlowARUITests.swift
new file mode 100644
index 000000000..ea4994f19
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowARUITests/FlowARUITests.swift
@@ -0,0 +1,43 @@
+//
+// FlowARUITests.swift
+// FlowARUITests
+//
+// Created by 周舒意 on 2020/10/20.
+// Copyright © 2020 周舒意. All rights reserved.
+//
+
+import XCTest
+
+class FlowARUITests: XCTestCase {
+
+ override func setUp() {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ // In UI tests it is usually best to stop immediately when a failure occurs.
+ continueAfterFailure = false
+
+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+ }
+
+ override func tearDown() {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func testExample() {
+ // UI tests must launch the application that they test.
+ let app = XCUIApplication()
+ app.launch()
+
+ // Use recording to get started writing UI tests.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ }
+
+ func testLaunchPerformance() {
+ if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
+ // This measures how long it takes to launch your application.
+ measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
+ XCUIApplication().launch()
+ }
+ }
+ }
+}
diff --git a/contrib/ar4dcops/app_ios/FlowARUITests/Info.plist b/contrib/ar4dcops/app_ios/FlowARUITests/Info.plist
new file mode 100644
index 000000000..64d65ca49
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/FlowARUITests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/contrib/ar4dcops/app_ios/README.md b/contrib/ar4dcops/app_ios/README.md
new file mode 100644
index 000000000..19ec58c57
--- /dev/null
+++ b/contrib/ar4dcops/app_ios/README.md
@@ -0,0 +1,6 @@
+# App-IOS
+
+An Augmented Reality (AR) based app that runs on iOS systems to aid data center maintenance and audit work. It obtains data from
+the back-end server Flowgate, which integrates all the information related to a specific data
+center, and displays necessary information onto real scenes" in the data center using AR
+technology.
\ No newline at end of file