├── .gitignore ├── .idea ├── encodings.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENSE ├── README.md ├── Visualization ├── ARCore_point_cloud.txt ├── ARCore_sensor_pose.txt ├── FigureRotator.m ├── angle2rotmtx.m ├── main_script.m ├── plot_inertial_frame.m ├── plot_sensor_ARCore_frame.m ├── q2r.m ├── r2q.m ├── rotmtx2angle.m └── test_codes.m ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pjinkim │ │ └── arcore_data_logger │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── com │ │ │ └── pjinkim │ │ │ └── arcore_data_logger │ │ │ ├── ARCoreSession.java │ │ │ ├── AccumulatedPointCloud.java │ │ │ ├── FileStreamer.java │ │ │ ├── MainActivity.java │ │ │ ├── OutputDirectoryManager.java │ │ │ ├── PointCloudNode.java │ │ │ └── WorldToScreenTranslator.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── gruvi_logo.png │ │ ├── ic_launcher_background.xml │ │ └── sfu_logo.png │ │ ├── font │ │ ├── roboto.xml │ │ └── roboto_black.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── font_certs.xml │ │ ├── preloaded_fonts.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── pjinkim │ └── arcore_data_logger │ └── ExampleUnitTest.java ├── build.gradle ├── data_visualization.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Text files 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # IntelliJ 38 | *.iml 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/assetWizardSettings.xml 43 | .idea/dictionaries 44 | .idea/libraries 45 | .idea/caches 46 | 47 | # Keystore files 48 | # Uncomment the following line if you do not want to check your keystore files in. 49 | #*.jks 50 | 51 | # External native build folder generated in Android Studio 2.2 and later 52 | .externalNativeBuild 53 | 54 | # Google Services (e.g. APIs or Firebase) 55 | google-services.json 56 | 57 | # Freeline 58 | freeline.py 59 | freeline/ 60 | freeline_project_description.json 61 | 62 | # fastlane 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | fastlane/readme.md 68 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Pyojin Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ARCore Data Logger # 2 | 3 | This is a simple application to capture ARCore motion estimation (Visual-Inertial Odometry) results on Android devices for offline use. 4 | I want to play around with data from Google's Visual-Inertial Odometry (VIO) solution with ARCore framework API in Android Studio 3.4.2, API level 28 for Android devices. 5 | 6 | ![ARCore Data Logger](https://github.com/PyojinKim/ARCore-Data-Logger/blob/master/screenshot.png) 7 | 8 | For more details, see the ARCore documentation [here](https://developers.google.com/ar/reference/java/arcore/reference/com/google/ar/core/Frame). 9 | 10 | 11 | ## Usage Notes ## 12 | 13 | The txt files are produced automatically after pressing Stop button. 14 | This project is written Java under Android Studio Version 3.4.2 for Android 9.0 (API level 28) tested with Google Pixel 2 XL. 15 | It doesn't currently check for sensor availability before logging. 16 | 17 | 18 | ## Reference Frames and Device Attitude ## 19 | 20 | In the world (reference) coordinate space in ARCore, the Y-axis (up) always has +Y pointing up relative to gravity direction. 21 | For the Z-axis, ARCore chooses a basis vector (0,0,-1) pointing in the direction the device camera faces and perpendicular to the gravity axis. 22 | ARCore chooses a X-axis based on the Z- and Y-axes using the right hand rule. 23 | When a device is held in its default (portrait) orientation, the ARCore Android sensor frame's X-axis is horizontal and points to the right, the Y-axis is vertical and points up, and the Z-axis points toward the outside of the screen face. 24 | In this system, coordinates behind the screen have negative Z values. 25 | 26 | 27 | ## Output Format ## 28 | 29 | I have chosen the following output formats, but they are easy to modify if you find something else more convenient. 30 | 31 | * ARCore 6-DoF Sensor Pose (ARCore_sensor_pose.txt): `timestamp, q_x, q_y, q_z, q_w, t_x, t_y, t_z \n` 32 | * ARCore 3D Point Cloud (ARCore_point_cloud.txt): `position_x, position_y, position_z, color_R, color_G, color_B \n` 33 | 34 | Note that ARCore_sensor_pose.txt contains a N x 8 table, where N is the number of frames of this sequence. 35 | Row i represents the i'th pose of the [Android Sensor Coordinate System](https://developer.android.com/guide/topics/sensors/sensors_overview#sensors-coords) in the world coordinate space for this frame. 36 | 37 | 38 | ## Offline MATLAB Visualization ## 39 | 40 | The ability to experiment with different algorithms to process the ARCore (VIO) motion estimation results is the reason that I created this project in the first place. 41 | I have included an example script that you can use to parse and visualize the data that comes from ARCore Data Logger. 42 | Look under the Visualization directory to check it out. 43 | You can run the script by typing the following in your terminal: 44 | 45 | run main_script.m 46 | 47 | Here's one of the figures produced by the MATLAB script: 48 | 49 | ![Data visualization](https://github.com/PyojinKim/ARCore-Data-Logger/blob/master/data_visualization.png) 50 | 51 | -------------------------------------------------------------------------------- /Visualization/ARCore_sensor_pose.txt: -------------------------------------------------------------------------------- 1 | # Created at Mon Sep 09 23:13:04 PDT 2019 in Burnaby Canada 2 | 15492865785374 -0.234327 0.253666 0.046505 0.937327 -0.108027 0.009575 0.094321 3 | 15492949094847 -0.235978 0.255137 0.047185 0.936479 -0.109231 0.009118 0.095799 4 | 15492999052122 -0.236993 0.256834 0.048032 0.935716 -0.110436 0.008651 0.097048 5 | 15493049045474 -0.237356 0.258507 0.049047 0.935110 -0.113152 0.008718 0.099078 6 | 15493099002749 -0.237297 0.260893 0.050438 0.934388 -0.115710 0.008375 0.101342 7 | 15493132424307 -0.237523 0.263131 0.051251 0.933658 -0.117721 0.008314 0.103419 8 | 15493182412100 -0.237621 0.267502 0.051791 0.932361 -0.121610 0.008099 0.106655 9 | 15493232369376 -0.237079 0.273510 0.051170 0.930789 -0.126307 0.007773 0.110572 10 | 15493282419710 -0.236064 0.279639 0.050123 0.929281 -0.132243 0.007311 0.114676 11 | 15493315714387 -0.235368 0.283145 0.049592 0.928424 -0.136439 0.006850 0.117891 12 | 15493365570959 -0.235189 0.288851 0.049241 0.926729 -0.143355 0.006754 0.123601 13 | 15493398896154 -0.235546 0.293391 0.049167 0.925215 -0.147894 0.006497 0.127407 14 | 15493465346963 -0.236082 0.303120 0.049594 0.921913 -0.157359 0.006057 0.134440 15 | 15493498672158 -0.235859 0.308029 0.049193 0.920364 -0.162077 0.005873 0.138406 16 | 15493565316487 -0.237223 0.318093 0.047436 0.916674 -0.171900 0.005540 0.146098 17 | 15493615273763 -0.238410 0.325932 0.045233 0.913719 -0.179024 0.005310 0.151880 18 | 15493665227153 -0.240766 0.333624 0.044094 0.910375 -0.186158 0.004843 0.157424 19 | 15493698552348 -0.242054 0.338582 0.043837 0.908213 -0.190381 0.004852 0.161203 20 | 15493748552793 -0.243011 0.344817 0.044391 0.905581 -0.196444 0.004881 0.166576 21 | 15493798510069 -0.243064 0.350280 0.045878 0.903393 -0.201208 0.005237 0.171356 22 | 15493831835264 -0.242869 0.353438 0.046931 0.902161 -0.203537 0.005589 0.174023 23 | 15493865197660 -0.241873 0.356012 0.047702 0.901375 -0.205180 0.005799 0.176252 24 | 15493915154935 -0.240483 0.357923 0.048612 0.900941 -0.206160 0.006329 0.178156 25 | 15493965134158 -0.238822 0.358223 0.048571 0.901267 -0.205250 0.006640 0.179098 26 | 15494015121951 -0.237411 0.356467 0.047881 0.902372 -0.202871 0.006663 0.177819 27 | 15494064993780 -0.237693 0.352550 0.046812 0.903891 -0.199493 0.007755 0.177379 28 | 15494114981573 -0.238245 0.347796 0.045897 0.905633 -0.195384 0.007399 0.173961 29 | 15494164898600 -0.238270 0.342513 0.044570 0.907704 -0.192147 0.006013 0.167928 30 | 15494214855875 -0.238194 0.337391 0.041896 0.909767 -0.186688 0.005291 0.163689 31 | 15494264658393 -0.237738 0.331139 0.039457 0.912289 -0.180718 0.004444 0.159007 32 | 15494314676703 -0.238410 0.324546 0.038136 0.914536 -0.174864 0.003322 0.154415 33 | 15494364601657 -0.239757 0.319521 0.036728 0.916010 -0.169506 0.002472 0.148911 34 | 15494414558933 -0.239187 0.315584 0.034940 0.917592 -0.163315 0.002151 0.144924 35 | 15494447905218 -0.238320 0.312884 0.033815 0.918784 -0.159842 0.002125 0.141591 36 | 15494497862494 -0.234075 0.308821 0.031508 0.921328 -0.153451 0.002191 0.137951 37 | 15494547884794 -0.231207 0.303572 0.030285 0.923835 -0.147393 0.002092 0.133559 38 | 15494597872587 -0.229948 0.297506 0.030046 0.926127 -0.140733 0.001999 0.129777 39 | 15494647869667 -0.228999 0.291147 0.029704 0.928391 -0.133963 0.002267 0.126145 40 | 15494697826942 -0.229074 0.284624 0.029602 0.930397 -0.127413 0.002100 0.121935 41 | 15494747763851 -0.228395 0.279502 0.029086 0.932131 -0.120062 0.001220 0.117452 42 | 15494797751644 -0.226995 0.273747 0.028087 0.934209 -0.113419 0.000696 0.113176 43 | 15494864363867 -0.226848 0.265782 0.027862 0.936549 -0.104435 -0.000737 0.107511 44 | 15494930983740 -0.227201 0.256506 0.028253 0.939035 -0.095770 -0.002313 0.101950 45 | 15494980906590 -0.226431 0.248953 0.028996 0.941228 -0.089761 -0.003757 0.098174 46 | 15495014201267 -0.225253 0.244275 0.029656 0.942715 -0.085465 -0.004850 0.095709 47 | 15495047464738 -0.223608 0.239094 0.030772 0.944398 -0.080636 -0.006425 0.093552 48 | 15495097422013 -0.223261 0.231329 0.031915 0.946373 -0.073969 -0.008310 0.089466 49 | 15495147317285 -0.223222 0.225481 0.031263 0.947815 -0.066949 -0.010153 0.085864 50 | 15495180642480 -0.223214 0.220746 0.031158 0.948934 -0.062288 -0.011360 0.083280 51 | 15495230599756 -0.223106 0.212189 0.031180 0.950909 -0.055329 -0.013004 0.079321 52 | 15495263897871 -0.223142 0.206485 0.030968 0.952162 -0.050971 -0.014108 0.076362 53 | 15495297192549 -0.223348 0.201455 0.030582 0.953203 -0.046245 -0.015004 0.073697 54 | 15495347195358 -0.223223 0.193387 0.030062 0.954918 -0.039369 -0.016035 0.069834 55 | 15495380520554 -0.222255 0.188140 0.029741 0.956202 -0.034439 -0.016636 0.067103 56 | 15495430508347 -0.220625 0.180002 0.029704 0.958145 -0.026815 -0.017223 0.062986 57 | 15495480497265 -0.219133 0.171590 0.029384 0.960039 -0.019044 -0.017393 0.058835 58 | 15495513791943 -0.216946 0.165785 0.028841 0.961571 -0.013345 -0.017277 0.056067 59 | 15495563763344 -0.214380 0.156302 0.027868 0.963760 -0.005270 -0.016745 0.051789 60 | 15495613751137 -0.212777 0.146741 0.025887 0.965672 0.004149 -0.016163 0.047608 61 | 15495647059083 -0.210563 0.139691 0.023977 0.967251 0.009658 -0.015719 0.044103 62 | 15495697016359 -0.207292 0.128578 0.021322 0.969558 0.019498 -0.014478 0.040002 63 | 15495747011021 -0.204241 0.115887 0.018838 0.971854 0.028454 -0.012619 0.035417 64 | 15495796968297 -0.200984 0.103249 0.016284 0.974002 0.038652 -0.010891 0.031497 65 | 15495830262974 -0.199385 0.095178 0.014572 0.975179 0.045500 -0.009776 0.028964 66 | 15495880189062 -0.197066 0.081728 0.012067 0.976903 0.054962 -0.008133 0.024405 67 | 15495930146338 -0.195921 0.066970 0.010280 0.978276 0.063645 -0.006754 0.019936 68 | 15495980105940 -0.196714 0.053197 0.008549 0.978979 0.072782 -0.005755 0.015551 69 | 15496030093733 -0.195990 0.042816 0.005448 0.979656 0.081509 -0.004848 0.011373 70 | 15496079998131 -0.197206 0.034340 0.001732 0.979759 0.089608 -0.003937 0.006884 71 | 15496129955406 -0.198440 0.024321 -0.000590 0.979811 0.097076 -0.003532 0.002258 72 | 15496179956263 -0.198780 0.013930 -0.002475 0.979942 0.104187 -0.002870 -0.001619 73 | 15496229913538 -0.199007 0.005106 -0.004734 0.979973 0.111322 -0.002397 -0.005496 74 | 15496279968097 -0.198091 -0.002748 -0.006950 0.980155 0.117492 -0.001440 -0.008905 75 | 15496346618488 -0.195307 -0.013591 -0.008800 0.980609 0.126218 -0.000218 -0.012752 76 | 15496396613902 -0.193579 -0.020151 -0.010050 0.980826 0.131732 0.001340 -0.015015 77 | 15496446571178 -0.191634 -0.025804 -0.011376 0.981061 0.137147 0.002980 -0.017345 78 | 15496496607103 -0.190224 -0.031669 -0.012462 0.981151 0.141039 0.004875 -0.019869 79 | 15496546564378 -0.189234 -0.036546 -0.013859 0.981154 0.143342 0.006898 -0.021764 80 | 15496579832647 -0.188481 -0.039611 -0.014721 0.981167 0.145541 0.008250 -0.022908 81 | 15496629820440 -0.187575 -0.044753 -0.015478 0.981108 0.148607 0.010373 -0.024340 82 | 15496696342646 -0.185587 -0.050817 -0.015620 0.981189 0.150332 0.013584 -0.025494 83 | 15496729637324 -0.184552 -0.053633 -0.015275 0.981239 0.151628 0.015381 -0.025629 84 | 15496779457481 -0.182446 -0.057391 -0.014870 0.981427 0.151271 0.018590 -0.025737 85 | 15496812782677 -0.180395 -0.059174 -0.014869 0.981700 0.151878 0.021198 -0.025259 86 | 15496862805364 -0.176072 -0.061853 -0.014774 0.982321 0.152518 0.025910 -0.024403 87 | 15496912762639 -0.172073 -0.064955 -0.014464 0.982834 0.152736 0.031147 -0.023369 88 | 15496946087834 -0.169373 -0.067256 -0.014514 0.983147 0.152811 0.035072 -0.023353 89 | 15496996084065 -0.164129 -0.070924 -0.014677 0.983776 0.152587 0.041200 -0.022130 90 | 15497046117665 -0.158370 -0.075505 -0.015486 0.984367 0.152392 0.047798 -0.020911 91 | 15497079412342 -0.155835 -0.078586 -0.016326 0.984517 0.152372 0.052172 -0.020396 92 | 15497129369618 -0.150237 -0.082562 -0.017432 0.985042 0.152309 0.059005 -0.019334 93 | 15497162826742 -0.144793 -0.086640 -0.017502 0.985506 0.151721 0.063997 -0.017881 94 | 15497196121420 -0.138423 -0.090208 -0.017400 0.986103 0.151808 0.068737 -0.016953 95 | 15497246109212 -0.128230 -0.095210 -0.016698 0.987022 0.151625 0.076129 -0.015092 96 | 15497296087338 -0.121024 -0.098939 -0.015610 0.987583 0.151837 0.083318 -0.013457 97 | 15497329382016 -0.116885 -0.100251 -0.015203 0.987956 0.151960 0.088038 -0.012097 98 | 15497379280408 -0.111308 -0.102819 -0.014793 0.988342 0.151572 0.095078 -0.009487 99 | 15497429268201 -0.104961 -0.104619 -0.014327 0.988854 0.151349 0.101755 -0.006248 100 | 15497479243787 -0.100807 -0.103815 -0.014778 0.989364 0.150668 0.108117 -0.002120 101 | 15497529201062 -0.096782 -0.104125 -0.014024 0.989745 0.150320 0.113902 0.002244 102 | 15497562540386 -0.092659 -0.105967 -0.012747 0.989961 0.149944 0.117785 0.005626 103 | 15497612497661 -0.087020 -0.109509 -0.011389 0.990104 0.149843 0.122767 0.010576 104 | 15497645774791 -0.083750 -0.111037 -0.011009 0.990220 0.149573 0.125976 0.014459 105 | 15497695732067 -0.079585 -0.113049 -0.010865 0.990337 0.149789 0.128995 0.019463 106 | 15497729057262 -0.077479 -0.114270 -0.010759 0.990365 0.150163 0.130123 0.022545 107 | 15497779060227 -0.076298 -0.117148 -0.010525 0.990123 0.150778 0.130489 0.026365 108 | 15497829017184 -0.075377 -0.120328 -0.010608 0.989812 0.151753 0.128942 0.029437 109 | 15497878974460 -0.077087 -0.123463 -0.010608 0.989294 0.153099 0.125873 0.031784 110 | 15497912299655 -0.078391 -0.124573 -0.011008 0.989048 0.154098 0.122973 0.032978 111 | 15497962236635 -0.080608 -0.125092 -0.013117 0.988778 0.155828 0.117931 0.035080 112 | 15498012224428 -0.083026 -0.125121 -0.016548 0.988523 0.157843 0.111048 0.035953 113 | 15498045573198 -0.085586 -0.125510 -0.018989 0.988211 0.158869 0.105986 0.036633 114 | 15498078867876 -0.089002 -0.126262 -0.021324 0.987766 0.160151 0.100303 0.036665 115 | 15498112193071 -0.093066 -0.127155 -0.023972 0.987216 0.161374 0.094196 0.036501 116 | 15498178802418 -0.101428 -0.130019 -0.029505 0.985869 0.162952 0.081519 0.036459 117 | 15498212097096 -0.106974 -0.131867 -0.031919 0.984961 0.163929 0.074683 0.035581 118 | 15498245340315 -0.113662 -0.135022 -0.033880 0.983719 0.164554 0.068073 0.035446 119 | 15498295328108 -0.122133 -0.139603 -0.036121 0.981982 0.166124 0.058183 0.033819 120 | 15498328653303 -0.127780 -0.142120 -0.037576 0.980848 0.167251 0.052055 0.032620 121 | 15498378554691 -0.135238 -0.146591 -0.039954 0.979094 0.168476 0.043853 0.032017 122 | 15498411849368 -0.139058 -0.149620 -0.041450 0.978038 0.169570 0.038671 0.030802 123 | 15498445115711 -0.142395 -0.152446 -0.043189 0.977046 0.170544 0.034028 0.032507 124 | 15498495072986 -0.147880 -0.155701 -0.044935 0.975638 0.171972 0.027168 0.030969 125 | 15498528398181 -0.152143 -0.157939 -0.045177 0.974611 0.172693 0.022836 0.029829 126 | 15498561741220 -0.156525 -0.159212 -0.045142 0.973711 0.173016 0.019117 0.030758 127 | 15498611759531 -0.163729 -0.156727 -0.044827 0.972944 0.172733 0.013499 0.029888 128 | 15498645077749 -0.168508 -0.153336 -0.044360 0.972690 0.171792 0.009992 0.029437 129 | 15498695035025 -0.174618 -0.146801 -0.044393 0.972619 0.169571 0.005556 0.028678 130 | 15498728360220 -0.177796 -0.141001 -0.044144 0.972912 0.167393 0.003091 0.028328 131 | 15498761772504 -0.181091 -0.134839 -0.043013 0.973229 0.164670 0.000644 0.026914 132 | 15498811760297 -0.184719 -0.124596 -0.041311 0.973986 0.159938 -0.001904 0.026809 133 | 15498861776820 -0.186767 -0.111652 -0.039561 0.975237 0.154678 -0.004064 0.025972 134 | 15498911734095 -0.187321 -0.098974 -0.036980 0.976600 0.148456 -0.005622 0.026864 135 | 15498945059290 -0.187037 -0.088101 -0.035477 0.977751 0.144050 -0.006628 0.026750 136 | 15498994950048 -0.185407 -0.069365 -0.033790 0.979628 0.136609 -0.007517 0.028007 137 | 15499044838539 -0.183957 -0.049202 -0.031446 0.981198 0.129538 -0.007549 0.027644 138 | 15499094795815 -0.184539 -0.027020 -0.029639 0.982006 0.121607 -0.007769 0.029203 139 | 15499128009737 -0.186233 -0.012426 -0.028087 0.982026 0.115902 -0.008063 0.030025 140 | 15499177997530 -0.189172 0.007048 -0.025815 0.981579 0.106861 -0.008298 0.031901 141 | 15499211322725 -0.190231 0.018050 -0.023882 0.981283 0.100360 -0.008457 0.033665 142 | 15499261398489 -0.190550 0.032303 -0.021123 0.980918 0.090024 -0.008579 0.036707 143 | 15499294723684 -0.190033 0.039706 -0.018943 0.980791 0.082516 -0.008857 0.039477 144 | 15499344687315 -0.189153 0.050655 -0.015526 0.980517 0.071268 -0.009056 0.044253 145 | 15499377981993 -0.188684 0.058959 -0.013480 0.980174 0.062880 -0.009274 0.048149 146 | 15499411307188 -0.188020 0.067293 -0.011832 0.979786 0.054132 -0.009603 0.052488 147 | 15499461344993 -0.187639 0.079460 -0.010116 0.978966 0.040063 -0.009964 0.058304 148 | 15499494670189 -0.187771 0.086536 -0.008765 0.978354 0.030277 -0.010315 0.063024 149 | 15499544608967 -0.189884 0.098903 -0.007056 0.976787 0.014915 -0.010179 0.068708 150 | 15499611228840 -0.192259 0.117971 -0.006090 0.974209 -0.005671 -0.010179 0.079648 151 | 15499644497417 -0.194954 0.127208 -0.005859 0.972511 -0.016201 -0.010221 0.084037 152 | 15499711147807 -0.204572 0.143013 -0.003862 0.968340 -0.037411 -0.010449 0.096774 153 | 15499744515975 -0.206664 0.150789 0.001113 0.966722 -0.048506 -0.011703 0.111064 154 | 15499794473250 -0.210646 0.164736 0.004544 0.963571 -0.064160 -0.011928 0.121790 155 | 15499827798445 -0.212441 0.173865 0.007396 0.961553 -0.074314 -0.012048 0.129328 156 | 15499877888239 -0.217271 0.187122 0.011359 0.957940 -0.091721 -0.005883 0.145180 157 | 15499911213434 -0.217591 0.197503 0.014878 0.955733 -0.100948 -0.005852 0.153076 158 | 15499961179052 -0.216286 0.209633 0.019908 0.953351 -0.114066 -0.006019 0.165645 159 | 15500011166844 -0.217797 0.219359 0.025652 0.950678 -0.126380 -0.006094 0.176643 160 | 15500044502618 -0.219443 0.225721 0.027936 0.948744 -0.133932 -0.003678 0.184572 161 | 15500094459893 -0.221033 0.233073 0.032212 0.946458 -0.145065 -0.003828 0.193692 162 | 15500144482538 -0.225092 0.235902 0.038194 0.944577 -0.155738 -0.004723 0.201537 163 | 15500177807733 -0.228622 0.237863 0.041258 0.943107 -0.162453 -0.004748 0.205916 164 | 15500227765008 -0.230320 0.239034 0.043848 0.942281 -0.172300 -0.004917 0.211911 165 | 15500261078082 -0.231635 0.239550 0.044742 0.941785 -0.178517 -0.005472 0.215630 166 | 15500311096393 -0.233326 0.241178 0.044533 0.940962 -0.187753 -0.005739 0.221382 167 | 15500361026518 -0.234416 0.243510 0.043233 0.940150 -0.198022 -0.006250 0.227269 168 | 15500394321196 -0.236177 0.245807 0.042588 0.939141 -0.203937 -0.006087 0.230968 169 | 15500444237040 -0.238856 0.249396 0.041270 0.937575 -0.212421 -0.005275 0.236927 170 | 15500494194315 -0.240493 0.253788 0.039203 0.936065 -0.220487 -0.004494 0.241856 171 | 15500527521662 -0.241845 0.256870 0.038313 0.934912 -0.226129 -0.003390 0.244488 172 | 15500577509455 -0.242285 0.260417 0.036509 0.933889 -0.233576 -0.002083 0.248834 173 | 15500627466731 -0.242679 0.262216 0.034611 0.933355 -0.240975 -0.000788 0.252778 174 | 15500660829608 -0.243420 0.264113 0.033252 0.932676 -0.245656 0.000424 0.254990 175 | 15500710786883 -0.245825 0.267591 0.032043 0.931095 -0.252438 0.001968 0.258100 176 | 15500760783251 -0.245682 0.270626 0.030274 0.930315 -0.258990 0.004275 0.260886 177 | 15500810740526 -0.243933 0.274253 0.028168 0.929779 -0.264150 0.006457 0.263672 178 | 15500844064170 -0.243042 0.276451 0.027201 0.929390 -0.267461 0.007961 0.264104 179 | 15500894051962 -0.242612 0.279843 0.026174 0.928516 -0.270773 0.010038 0.265387 180 | 15500927377158 -0.242159 0.282375 0.024896 0.927903 -0.271733 0.011134 0.265650 181 | 15500977337399 -0.240339 0.287065 0.021801 0.927014 -0.270627 0.012866 0.264253 182 | 15501010632077 -0.239046 0.290223 0.019615 0.926414 -0.268193 0.012802 0.262466 183 | 15501060589800 -0.240016 0.291997 0.018267 0.925633 -0.262890 0.012152 0.256852 184 | 15501093884478 -0.239986 0.291358 0.017644 0.925854 -0.257794 0.011753 0.253132 185 | 15501143804776 -0.236681 0.289998 0.015488 0.927170 -0.247883 0.011245 0.245982 186 | 15501177160489 -0.236234 0.288790 0.015278 0.927664 -0.241359 0.010877 0.240991 187 | 15501227117764 -0.236253 0.286910 0.014101 0.928261 -0.230687 0.010436 0.233392 188 | 15501276996591 -0.231337 0.282333 0.011345 0.930936 -0.219927 0.010042 0.225630 189 | 15501310321786 -0.227424 0.278629 0.009449 0.933035 -0.211994 0.009486 0.220712 190 | 15501343613499 -0.222721 0.274385 0.007654 0.935441 -0.203127 0.008621 0.215723 191 | 15501393570775 -0.215789 0.268823 0.006392 0.938684 -0.188400 0.006235 0.207770 192 | 15501426865452 -0.214471 0.265977 0.007682 0.939787 -0.177664 0.004479 0.202051 193 | 15501460175311 -0.212046 0.260838 0.009858 0.941755 -0.166678 0.002835 0.196394 194 | 15501510132587 -0.206235 0.254624 0.013169 0.944701 -0.149354 0.000291 0.187981 195 | 15501560011580 -0.204622 0.246921 0.018886 0.946997 -0.132647 -0.001834 0.178909 196 | 15501609999373 -0.201352 0.236717 0.023793 0.950187 -0.116789 -0.003785 0.171070 197 | 15501643103052 -0.200001 0.230340 0.026089 0.951978 -0.106693 -0.004942 0.166737 198 | 15501693060327 -0.200437 0.223265 0.024957 0.953601 -0.091255 -0.006766 0.159942 199 | 15501726416040 -0.199483 0.217640 0.023837 0.955129 -0.081291 -0.008243 0.155885 200 | 15501776400094 -0.200344 0.209475 0.023983 0.956769 -0.067938 -0.010232 0.150029 201 | 15501826357370 -0.200416 0.201771 0.023914 0.958410 -0.054382 -0.012474 0.144678 202 | 15501859728506 -0.201155 0.197204 0.023909 0.959206 -0.046025 -0.013758 0.140103 203 | 15501909685782 -0.203981 0.191601 0.024654 0.959725 -0.032859 -0.015756 0.134789 204 | 15501943014542 -0.203648 0.187951 0.025018 0.960508 -0.024427 -0.016336 0.131729 205 | 15501992971817 -0.202216 0.182359 0.025572 0.961873 -0.011212 -0.017893 0.126354 206 | 15502043000153 -0.201338 0.177130 0.026480 0.963009 0.001658 -0.018618 0.120409 207 | 15502076325349 -0.198524 0.174550 0.026627 0.964060 0.011075 -0.018872 0.116762 208 | 15502126282624 -0.196211 0.168164 0.026445 0.965672 0.025593 -0.018715 0.111563 209 | 15502176309821 -0.195204 0.157222 0.027307 0.967694 0.039548 -0.017825 0.105614 210 | 15502226297614 -0.196550 0.149253 0.025661 0.968728 0.054403 -0.017586 0.099860 211 | 15502259616174 -0.198125 0.144875 0.022514 0.969149 0.062820 -0.017896 0.093876 212 | 15502309603967 -0.202015 0.135824 0.016523 0.969778 0.077555 -0.018640 0.086462 213 | 15502359582090 -0.202831 0.127394 0.011108 0.970828 0.092713 -0.018836 0.078960 214 | 15502409539365 -0.200604 0.122318 0.006572 0.971984 0.107873 -0.020031 0.071644 215 | 15502459532343 -0.197910 0.112732 0.004235 0.973707 0.121919 -0.021446 0.062152 216 | 15502509489618 -0.191934 0.107705 0.006415 0.975459 0.137569 -0.023238 0.054817 217 | 15502542949373 -0.188528 0.107704 0.011185 0.976080 0.148507 -0.024875 0.048390 218 | 15502592937166 -0.182670 0.109510 0.015010 0.976941 0.165399 -0.027444 0.040725 219 | 15502642939693 -0.181156 0.111888 0.013059 0.976982 0.180301 -0.029572 0.031972 220 | 15502692927486 -0.180871 0.112093 0.011274 0.977033 0.195623 -0.031725 0.024509 221 | 15502742864561 -0.180866 0.109126 0.009530 0.977389 0.210328 -0.033410 0.016509 222 | 15502792821837 -0.183281 0.103134 0.009862 0.977586 0.225634 -0.035083 0.009495 223 | 15502826147032 -0.185819 0.098435 0.009481 0.977595 0.234600 -0.036113 0.003900 224 | 15502876159934 -0.186225 0.096741 0.009556 0.977686 0.250417 -0.037675 -0.001540 225 | 15502926117209 -0.188763 0.094142 0.009184 0.977457 0.266235 -0.039190 -0.006191 226 | 15502959594271 -0.189757 0.091645 0.009069 0.977502 0.275423 -0.040420 -0.008777 227 | 15503009551546 -0.186633 0.089368 0.009766 0.978308 0.291577 -0.041874 -0.012860 228 | 15503076146673 -0.186156 0.086184 0.012702 0.978651 0.312848 -0.042156 -0.016986 229 | 15503109471868 -0.184934 0.084407 0.015204 0.979002 0.324056 -0.042068 -0.019876 230 | 15503159406772 -0.180193 0.084320 0.020416 0.979798 0.339335 -0.040838 -0.023646 231 | 15503192701450 -0.178540 0.083881 0.024558 0.980043 0.350102 -0.039939 -0.025953 232 | 15503242729236 -0.176190 0.085192 0.029501 0.980219 0.363821 -0.037405 -0.028495 233 | 15503276023914 -0.173631 0.091133 0.030612 0.980107 0.373432 -0.035797 -0.029453 234 | 15503325981189 -0.171718 0.101589 0.029501 0.979450 0.386905 -0.033243 -0.030203 235 | 15503359332007 -0.169489 0.110543 0.027028 0.978940 0.393670 -0.031050 -0.030229 236 | 15503409289283 -0.165970 0.120503 0.023913 0.978448 0.405046 -0.028614 -0.029946 237 | 15503459292079 -0.164863 0.130190 0.022146 0.977435 0.415258 -0.026131 -0.028884 238 | 15503509279872 -0.165941 0.134793 0.021383 0.976646 0.425129 -0.023670 -0.028722 239 | 15503542571508 -0.167012 0.136668 0.021318 0.976204 0.430535 -0.021470 -0.029081 240 | 15503592559301 -0.166543 0.135829 0.021873 0.976389 0.439757 -0.018515 -0.028733 241 | 15503642516577 -0.164134 0.131112 0.021849 0.977442 0.448565 -0.015023 -0.028295 242 | 15503692548440 -0.163135 0.124690 0.022595 0.978432 0.457661 -0.011298 -0.027711 243 | 15503742464956 -0.162346 0.120571 0.023654 0.979054 0.465666 -0.007056 -0.026512 244 | 15503792452749 -0.161805 0.120442 0.024182 0.979146 0.474238 -0.003100 -0.024777 245 | 15503842410025 -0.160397 0.121905 0.024314 0.979194 0.483013 0.000711 -0.022803 246 | 15503892401507 -0.158621 0.119508 0.023833 0.979790 0.491257 0.005249 -0.020878 247 | 15503942364043 -0.154635 0.116621 0.023201 0.980790 0.499489 0.009450 -0.019479 248 | 15503992351836 -0.151759 0.113393 0.022285 0.981639 0.507249 0.012696 -0.018374 249 | 15504042331552 -0.149300 0.110541 0.020542 0.982379 0.513817 0.015596 -0.017584 250 | 15504092288828 -0.149956 0.110424 0.018106 0.982340 0.518519 0.017212 -0.016602 251 | 15504142306380 -0.150208 0.111645 0.019664 0.982134 0.519344 0.017573 -0.014518 252 | 15504175631575 -0.148074 0.110043 0.021024 0.982610 0.517591 0.017389 -0.012763 253 | 15504225588851 -0.144711 0.106160 0.017831 0.983601 0.512350 0.016393 -0.010366 254 | 15504275589538 -0.149759 0.107978 0.011845 0.982737 0.503907 0.014684 -0.007723 255 | 15504325577331 -0.154886 0.122830 0.005467 0.980252 0.494026 0.011874 -0.004304 256 | 15504358858584 -0.158970 0.138149 0.000671 0.977570 0.486794 0.010747 -0.001616 257 | 15504408846377 -0.166625 0.154193 -0.005078 0.973876 0.473524 0.008818 0.001948 258 | 15504458647471 -0.165480 0.158893 -0.007417 0.973301 0.458739 0.007217 0.006676 259 | 15504491942149 -0.162524 0.158242 -0.006833 0.973909 0.448946 0.006317 0.009607 260 | 15504558854028 -0.157109 0.156851 -0.005388 0.975031 0.429245 0.006219 0.015275 261 | 15504592209740 -0.158203 0.158740 -0.004669 0.974552 0.419582 0.006313 0.017285 262 | 15504625504418 -0.160328 0.162519 -0.003788 0.973585 0.409486 0.006818 0.019431 263 | 15504692005921 -0.155782 0.173064 -0.002970 0.972508 0.386490 0.009039 0.024339 264 | 15504725331117 -0.154345 0.176478 -0.004361 0.972118 0.374499 0.010264 0.027021 265 | 15504758684146 -0.157349 0.175423 -0.005996 0.971819 0.361447 0.011473 0.029029 266 | 15504808641421 -0.160821 0.167700 -0.010153 0.972579 0.342994 0.012199 0.031313 267 | 15504841880412 -0.163948 0.159321 -0.011887 0.973446 0.329812 0.012448 0.032283 268 | 15504908530803 -0.167561 0.141434 -0.014667 0.975553 0.305680 0.012031 0.034822 269 | 15504958416586 -0.176040 0.135844 -0.018216 0.974794 0.288427 0.011917 0.036802 270 | 15505008404379 -0.181360 0.123605 -0.020555 0.975401 0.271534 0.011885 0.039268 271 | 15505041669517 -0.183181 0.117595 -0.021448 0.975785 0.260584 0.011753 0.041117 272 | 15505091657310 -0.188054 0.105859 -0.021685 0.976196 0.244697 0.011525 0.043694 273 | 15505141599818 -0.185789 0.100088 -0.022717 0.977215 0.228443 0.011330 0.047231 274 | 15505191587611 -0.187958 0.100552 -0.024243 0.976715 0.213144 0.010738 0.050657 275 | 15505241498731 -0.188717 0.099170 -0.024334 0.976708 0.197662 0.009552 0.053368 276 | 15505291486524 -0.189009 0.095833 -0.024250 0.976987 0.182873 0.008388 0.056343 277 | 15505324784052 -0.190332 0.097310 -0.024844 0.976569 0.172944 0.008355 0.057816 278 | 15505374741328 -0.190283 0.098600 -0.024789 0.976451 0.158551 0.008041 0.061247 279 | 15505424729121 -0.187709 0.098489 -0.023973 0.976980 0.144298 0.008169 0.065001 280 | 15505474668355 -0.186063 0.104269 -0.022944 0.976720 0.130443 0.009200 0.068562 281 | 15505524625631 -0.183670 0.110365 -0.022856 0.976505 0.116950 0.009706 0.073003 282 | 15505574580152 -0.183439 0.116146 -0.022840 0.975878 0.103253 0.010492 0.077413 283 | 15505624537427 -0.182940 0.119252 -0.022195 0.975612 0.089206 0.010852 0.082404 284 | 15505674478775 -0.182698 0.120679 -0.019879 0.975532 0.074033 0.011604 0.086504 285 | 15505724466568 -0.184012 0.124234 -0.018707 0.974862 0.059092 0.011931 0.091514 286 | 15505774466000 -0.188537 0.129118 -0.018376 0.973368 0.044132 0.012624 0.096482 287 | 15505824423275 -0.192467 0.133722 -0.018076 0.971982 0.029596 0.012630 0.101888 288 | 15505874382682 -0.197093 0.139818 -0.017021 0.970214 0.014661 0.013451 0.107675 289 | 15505924339958 -0.199493 0.144578 -0.014963 0.969059 0.001084 0.013220 0.113389 290 | 15505974289843 -0.202934 0.149746 -0.011671 0.967604 -0.012945 0.013705 0.119222 291 | 15506024277636 -0.204851 0.157185 -0.008911 0.966048 -0.026346 0.013883 0.125546 292 | 15506074213528 -0.206666 0.164446 -0.005939 0.964475 -0.039500 0.013536 0.131628 293 | 15506124170803 -0.210080 0.168355 -0.001403 0.963079 -0.052882 0.012629 0.137671 294 | 15506174130257 -0.215197 0.174160 0.002599 0.960912 -0.067646 0.011512 0.143439 295 | 15506207424935 -0.218203 0.176853 0.005594 0.959729 -0.077146 0.010359 0.147486 296 | 15506257405877 -0.224811 0.182289 0.010056 0.957147 -0.092190 0.008305 0.153170 297 | 15506290700555 -0.229583 0.186429 0.012428 0.955186 -0.102006 0.006689 0.157165 298 | 15506324025750 -0.232968 0.190365 0.014132 0.953565 -0.111683 0.005061 0.161260 299 | 15506374022453 -0.238378 0.198801 0.015793 0.950476 -0.126932 0.002801 0.167405 300 | 15506423979729 -0.244192 0.206553 0.016488 0.947330 -0.141301 0.000779 0.173448 301 | 15506473972973 -0.246668 0.219437 0.015931 0.943795 -0.156746 -0.000470 0.179782 302 | 15506507298168 -0.248802 0.229068 0.016949 0.940924 -0.166397 -0.001249 0.184281 303 | 15506540560076 -0.250110 0.236470 0.018857 0.938707 -0.177086 -0.001859 0.188669 304 | 15506590578387 -0.248801 0.246397 0.022258 0.936424 -0.192103 -0.002601 0.195567 305 | 15506640514940 -0.250404 0.253262 0.027098 0.934035 -0.207830 -0.003413 0.202191 306 | 15506673809618 -0.250212 0.257732 0.028946 0.932808 -0.217508 -0.003604 0.207143 307 | 15506707134813 -0.248621 0.261787 0.029531 0.932085 -0.227088 -0.003683 0.212207 308 | 15506757120783 -0.249156 0.266066 0.031328 0.930671 -0.241809 -0.003551 0.218561 309 | 15506807108576 -0.249855 0.273725 0.032964 0.928203 -0.255308 -0.002500 0.225380 310 | 15506840395980 -0.247778 0.280790 0.031832 0.926687 -0.263717 -0.001271 0.228869 311 | 15506890383773 -0.246290 0.287450 0.030321 0.925091 -0.275672 0.001053 0.235493 312 | 15506940392759 -0.244697 0.294667 0.026942 0.923346 -0.285981 0.004115 0.242245 313 | 15506973687437 -0.243372 0.299072 0.023931 0.922363 -0.292205 0.006381 0.246498 314 | 15507023644712 -0.242361 0.305510 0.019995 0.920611 -0.300371 0.009897 0.252725 315 | 15507056990792 -0.241541 0.309649 0.017458 0.919495 -0.304810 0.012558 0.256469 316 | 15507106948068 -0.239438 0.314393 0.012693 0.918512 -0.310029 0.016023 0.261402 317 | 15507156919645 -0.237358 0.315728 0.008140 0.918646 -0.313068 0.019476 0.264575 318 | 15507190244840 -0.237335 0.315063 0.006101 0.918896 -0.313936 0.021013 0.265168 319 | 15507240174928 -0.236295 0.314215 0.002415 0.919471 -0.311871 0.023330 0.263466 320 | 15507273500124 -0.235805 0.311946 0.000241 0.920372 -0.309164 0.023980 0.261205 321 | 15507306794802 -0.236397 0.308545 -0.001350 0.921366 -0.305395 0.024259 0.257828 322 | 15507356710288 -0.238041 0.303085 -0.003686 0.922747 -0.297715 0.025108 0.251135 323 | 15507406698081 -0.238069 0.295643 -0.006772 0.925134 -0.288358 0.025101 0.243032 324 | 15507439941203 -0.238357 0.290101 -0.007916 0.926803 -0.281343 0.025088 0.236834 325 | 15507489928996 -0.237596 0.282256 -0.008745 0.929410 -0.268898 0.024718 0.226477 326 | 15507523254191 -0.239964 0.277378 -0.008411 0.930273 -0.260368 0.024643 0.218950 327 | 15507573188122 -0.241637 0.268431 -0.010142 0.932445 -0.246130 0.023353 0.208709 328 | 15507606513318 -0.238859 0.261791 -0.011605 0.935028 -0.235840 0.022382 0.201986 329 | 15507656556819 -0.237208 0.253714 -0.012666 0.937657 -0.220356 0.021249 0.190961 330 | 15507689851497 -0.233774 0.248959 -0.013917 0.939774 -0.209124 0.020339 0.184884 331 | 15507739855033 -0.227550 0.244889 -0.012733 0.942384 -0.192346 0.019567 0.175299 332 | 15507773149711 -0.223294 0.242823 -0.010900 0.943959 -0.180795 0.018532 0.169986 333 | 15507806474906 -0.217451 0.239759 -0.008732 0.946126 -0.169185 0.017125 0.165105 334 | 15507839819769 -0.212645 0.235440 -0.004993 0.948327 -0.158292 0.015542 0.159812 335 | 15507889807562 -0.209633 0.233955 0.000749 0.949378 -0.140755 0.013163 0.153039 336 | 15507923102240 -0.207378 0.232061 0.004127 0.950329 -0.129196 0.011216 0.149034 337 | 15507973234320 -0.203908 0.228172 0.011455 0.951960 -0.112724 0.008104 0.143306 338 | 15508006559515 -0.204094 0.231193 0.016986 0.951108 -0.101108 0.005297 0.139855 339 | 15508056590945 -0.201281 0.232326 0.021187 0.951347 -0.085044 0.000606 0.133818 340 | 15508089916141 -0.198106 0.229848 0.022809 0.952577 -0.074964 -0.002432 0.130514 341 | 15508139846531 -0.196414 0.226501 0.025682 0.953656 -0.060482 -0.005903 0.125501 342 | 15508189803807 -0.193482 0.219702 0.027485 0.955793 -0.045918 -0.008788 0.120704 343 | 15508223100424 -0.190990 0.214055 0.028117 0.957556 -0.037204 -0.009685 0.116725 344 | 15508256395102 -0.189012 0.209789 0.028667 0.958875 -0.027877 -0.010728 0.113654 345 | 15508306413412 -0.186646 0.203041 0.028778 0.960786 -0.013812 -0.011968 0.109816 346 | 15508339687633 -0.186011 0.197453 0.029002 0.962066 -0.005196 -0.012149 0.107122 347 | 15508372982311 -0.185876 0.191357 0.028636 0.963334 0.003596 -0.012834 0.104985 348 | 15508422970104 -0.185559 0.184891 0.026859 0.964708 0.016641 -0.013826 0.102492 349 | 15508456232223 -0.185326 0.179368 0.025901 0.965821 0.024876 -0.013787 0.099639 350 | 15508506189499 -0.183938 0.172813 0.024565 0.967315 0.037598 -0.014521 0.097447 351 | 15508556197654 -0.183409 0.168915 0.023641 0.968127 0.050265 -0.014664 0.094701 352 | 15508589522849 -0.183697 0.167225 0.024046 0.968356 0.059280 -0.015634 0.092871 353 | 15508639480929 -0.184440 0.167632 0.025858 0.968097 0.071300 -0.018090 0.087642 354 | 15508689438204 -0.185556 0.164832 0.027910 0.968308 0.085443 -0.021011 0.083435 355 | 15508722732882 -0.185825 0.161700 0.028667 0.968762 0.093496 -0.022910 0.078853 356 | 15508756044371 -0.186174 0.157311 0.029282 0.969399 0.103119 -0.024757 0.075001 357 | 15508806001647 -0.186340 0.149909 0.029154 0.970544 0.117804 -0.027256 0.069123 358 | 15508855921787 -0.186144 0.141018 0.029518 0.971902 0.131022 -0.028755 0.060740 359 | 15508905940098 -0.184011 0.135989 0.029138 0.973035 0.146319 -0.029895 0.055881 360 | 15508955883662 -0.182040 0.130748 0.028764 0.974135 0.160972 -0.029991 0.050088 361 | 15509005840937 -0.181836 0.126585 0.028926 0.974718 0.176449 -0.029922 0.045971 362 | 15509039177838 -0.181211 0.123977 0.028099 0.975194 0.185747 -0.029263 0.042126 363 | 15509089135113 -0.181100 0.122536 0.025011 0.975480 0.201138 -0.028636 0.038417 364 | 15509122460308 -0.180882 0.122473 0.022537 0.975589 0.211537 -0.028383 0.036088 365 | 15509172514957 -0.178478 0.122066 0.018932 0.976159 0.225466 -0.026826 0.031392 366 | 15509205809635 -0.177890 0.121324 0.016796 0.976398 0.236239 -0.026945 0.028988 367 | 15509255809878 -0.175427 0.114028 0.015436 0.977745 0.251768 -0.026937 0.024366 368 | 15509305767154 -0.173417 0.101395 0.016107 0.979483 0.267766 -0.027393 0.019981 369 | 15509355669550 -0.171895 0.086725 0.019292 0.981101 0.283050 -0.027667 0.015569 370 | 15509388994745 -0.170150 0.078441 0.021839 0.982048 0.293226 -0.027815 0.012542 371 | 15509422181933 -0.166955 0.071985 0.024295 0.983033 0.303118 -0.027410 0.009385 372 | 15509472169726 -0.163087 0.064692 0.028639 0.984072 0.318430 -0.026874 0.005205 373 | 15509505494921 -0.161769 0.061294 0.031608 0.984416 0.328602 -0.026322 0.002481 374 | 15509555477195 -0.156430 0.062776 0.034502 0.985088 0.343282 -0.024893 -0.001092 375 | 15509588802390 -0.153836 0.067725 0.036253 0.985106 0.353054 -0.023742 -0.002749 376 | 15509638805368 -0.151934 0.073795 0.037918 0.984902 0.366117 -0.021406 -0.006259 377 | 15509688762644 -0.149797 0.072626 0.037871 0.985318 0.379604 -0.019826 -0.009229 378 | 15509722087839 -0.149871 0.067378 0.037584 0.985691 0.388406 -0.019018 -0.011307 379 | 15509772016110 -0.151479 0.061179 0.036173 0.985902 0.401612 -0.017564 -0.014403 380 | 15509821973385 -0.157443 0.060282 0.033447 0.985119 0.414388 -0.016330 -0.017654 381 | 15509855371982 -0.160895 0.057297 0.032535 0.984770 0.422969 -0.014901 -0.020669 382 | 15509905329257 -0.159311 0.056688 0.031947 0.985082 0.436160 -0.013491 -0.022698 383 | 15509938546591 -0.157385 0.060359 0.031445 0.985189 0.445001 -0.011859 -0.023625 384 | 15509988503867 -0.152782 0.073676 0.029534 0.985067 0.458300 -0.009949 -0.023025 385 | 15510021811398 -0.151230 0.083386 0.028677 0.984558 0.466825 -0.008084 -0.022912 386 | 15510071799191 -0.150559 0.092766 0.029235 0.983805 0.478844 -0.005427 -0.022668 387 | 15510105093869 -0.148716 0.099176 0.029831 0.983442 0.486640 -0.003700 -0.022274 388 | 15510155075445 -0.148731 0.103034 0.031307 0.982997 0.497099 -0.000180 -0.023167 389 | 15510205063238 -0.154019 0.103235 0.030662 0.982182 0.507555 0.003304 -0.023814 390 | 15510238469735 -0.157757 0.102767 0.030583 0.981640 0.515006 0.006264 -0.024323 391 | 15510288427010 -0.155858 0.098217 0.033603 0.982310 0.523998 0.010401 -0.023059 392 | 15510321782723 -0.151783 0.096464 0.035571 0.983052 0.529568 0.013002 -0.021516 393 | 15510371739200 -0.145690 0.097614 0.036606 0.983822 0.536945 0.017914 -0.020098 394 | 15510421696475 -0.140852 0.094025 0.038119 0.984818 0.542946 0.023115 -0.019592 395 | 15510455092538 -0.138630 0.087030 0.039505 0.985721 0.546549 0.026804 -0.019119 396 | 15510505049814 -0.133747 0.077945 0.042070 0.987049 0.550942 0.032228 -0.017906 397 | 15510554964791 -0.129474 0.072434 0.045954 0.987865 0.554087 0.038431 -0.016533 398 | 15510604983102 -0.126332 0.070895 0.049110 0.988232 0.556136 0.043533 -0.013996 399 | 15510638208371 -0.125409 0.070736 0.050045 0.988314 0.556739 0.047186 -0.013319 400 | 15510688165647 -0.125805 0.071190 0.048902 0.988288 0.556788 0.050498 -0.011610 401 | 15510721490842 -0.124723 0.071956 0.046888 0.988467 0.555826 0.051210 -0.010298 402 | 15510771456043 -0.122140 0.072724 0.042682 0.988924 0.551328 0.050399 -0.008482 403 | 15510804781239 -0.120831 0.072560 0.039110 0.989245 0.547517 0.048628 -0.007792 404 | 15510854689884 -0.121342 0.073036 0.032172 0.989397 0.539457 0.045453 -0.008166 405 | 15510904677677 -0.121468 0.069682 0.026920 0.989780 0.528923 0.040266 -0.008716 406 | 15510954653426 -0.125807 0.063723 0.023965 0.989716 0.515369 0.035018 -0.010315 407 | 15510987948104 -0.130339 0.063755 0.021451 0.989185 0.505455 0.030877 -0.010838 408 | 15511021273299 -0.137158 0.064513 0.020061 0.988243 0.494937 0.026517 -0.011516 409 | 15511054516509 -0.146089 0.061149 0.019500 0.987187 0.483038 0.023279 -0.012217 410 | 15511087841704 -0.155043 0.059433 0.018473 0.985945 0.471305 0.019479 -0.013097 411 | 15511121238793 -0.161342 0.059473 0.016911 0.984960 0.458731 0.015891 -0.014161 412 | 15511171257103 -0.167528 0.059039 0.014661 0.983989 0.440130 0.010944 -0.013139 413 | 15511204551781 -0.171360 0.061470 0.014570 0.983181 0.427543 0.008071 -0.011520 414 | 15511254508407 -0.175723 0.058804 0.015567 0.982558 0.408576 0.004835 -0.008845 415 | 15511304496200 -0.179132 0.059248 0.017465 0.981884 0.389910 0.002522 -0.006450 416 | 15511337955002 -0.183138 0.061772 0.018209 0.980976 0.377888 0.002628 -0.005722 417 | 15511387912278 -0.182922 0.067695 0.018119 0.980627 0.359390 0.003100 -0.002789 418 | 15511421237473 -0.183485 0.074686 0.019294 0.979991 0.346998 0.003925 -0.000366 419 | 15511454582114 -0.186168 0.083208 0.019855 0.978787 0.334452 0.005689 0.000922 420 | 15511504569907 -0.187984 0.095596 0.020565 0.977292 0.315529 0.007901 0.003980 421 | 15511554527183 -0.189288 0.101418 0.024706 0.976358 0.297075 0.009806 0.006820 422 | 15511604346172 -0.185439 0.105528 0.029378 0.976531 0.278084 0.012524 0.008757 423 | 15511637625200 -0.183264 0.105531 0.032605 0.976839 0.265490 0.014271 0.010238 424 | 15511687582476 -0.177317 0.109104 0.036236 0.977416 0.247121 0.017294 0.013879 425 | 15511720877154 -0.174962 0.113238 0.037972 0.977304 0.235010 0.019301 0.016578 426 | 15511770931435 -0.175351 0.121061 0.040343 0.976201 0.217200 0.022790 0.019489 427 | 15511820888710 -0.172470 0.128401 0.041854 0.975713 0.199948 0.025543 0.023940 428 | 15511854257929 -0.170551 0.131973 0.042385 0.975550 0.188093 0.027845 0.025492 429 | 15511904215204 -0.170080 0.138598 0.042885 0.974692 0.171135 0.029612 0.029387 430 | 15511954176777 -0.169658 0.143259 0.042468 0.974110 0.153432 0.030929 0.032883 431 | 15512004134052 -0.174347 0.148396 0.040276 0.972604 0.136363 0.030558 0.036505 432 | 15512054167539 -0.181591 0.154971 0.035620 0.970433 0.118936 0.029328 0.039991 433 | 15512104155332 -0.188343 0.156966 0.030139 0.969010 0.101394 0.026689 0.044006 434 | 15512137450010 -0.191964 0.157999 0.026215 0.968245 0.089196 0.025238 0.046084 435 | 15512187431483 -0.192969 0.156741 0.022351 0.968347 0.071110 0.022756 0.051521 436 | 15512237510588 -0.194484 0.159804 0.020389 0.967586 0.053092 0.021445 0.058010 437 | 15512287467863 -0.196723 0.166321 0.019276 0.966057 0.036000 0.020075 0.064788 438 | 15512337465999 -0.197884 0.168787 0.018407 0.965409 0.018240 0.020286 0.070894 439 | 15512387453792 -0.200327 0.174211 0.017370 0.963959 0.002312 0.019922 0.077738 440 | 15512420847145 -0.202540 0.177185 0.016728 0.962966 -0.008749 0.020258 0.082203 441 | 15512470804420 -0.206624 0.179868 0.017374 0.961588 -0.024190 0.019344 0.089231 442 | 15512504129616 -0.208134 0.180750 0.019121 0.961064 -0.034069 0.018327 0.094121 443 | 15512554094553 -0.211265 0.184512 0.021357 0.959618 -0.049407 0.017063 0.100332 444 | 15512587419748 -0.214443 0.191209 0.022892 0.957564 -0.059084 0.016047 0.105718 445 | 15512620744944 -0.217219 0.198571 0.024893 0.955388 -0.068497 0.014889 0.111260 446 | 15512670778466 -0.220052 0.210544 0.027193 0.952108 -0.082835 0.013567 0.119545 447 | 15512704073143 -0.220881 0.218227 0.028453 0.950147 -0.092093 0.012186 0.126109 448 | 15512737381126 -0.220807 0.225013 0.029424 0.948550 -0.101655 0.011012 0.132144 449 | 15512787368919 -0.222935 0.235450 0.031308 0.945454 -0.115817 0.008905 0.141272 450 | 15512820663597 -0.225766 0.242776 0.032240 0.942895 -0.125105 0.007701 0.147199 451 | 15512853974609 -0.229082 0.248104 0.032746 0.940688 -0.134459 0.007296 0.152333 452 | 15512903962402 -0.234774 0.251971 0.034653 0.938185 -0.148379 0.006332 0.160014 453 | 15512937429097 -0.238795 0.254433 0.037882 0.936379 -0.157604 0.006036 0.163438 454 | 15512987386373 -0.242202 0.257473 0.042435 0.934476 -0.170835 0.004771 0.168912 455 | 15513020681051 -0.244749 0.258767 0.045122 0.933328 -0.179600 0.003759 0.172956 456 | 15513054040524 -0.245943 0.259390 0.047875 0.932704 -0.188565 0.003269 0.176050 457 | 15513103997799 -0.244789 0.263284 0.050909 0.931756 -0.200669 0.003082 0.183136 458 | 15513137326339 -0.245664 0.266765 0.052665 0.930436 -0.209391 0.003522 0.185914 459 | 15513187314132 -0.244121 0.270940 0.053919 0.929564 -0.221085 0.004399 0.193259 460 | 15513220608809 -0.241104 0.272620 0.053994 0.929856 -0.228451 0.005075 0.198306 461 | 15513253927340 -0.238576 0.275177 0.053318 0.929793 -0.236114 0.006078 0.202092 462 | 15513303884615 -0.234630 0.279273 0.049922 0.929765 -0.246382 0.007130 0.209234 463 | 15513337130153 -0.231468 0.279698 0.046009 0.930631 -0.252378 0.008393 0.213421 464 | 15513387087429 -0.229296 0.279930 0.040136 0.931371 -0.260934 0.009736 0.219396 465 | 15513437124991 -0.228633 0.279727 0.033426 0.931860 -0.267774 0.012040 0.225216 466 | 15513487143302 -0.227759 0.277538 0.027991 0.932907 -0.274336 0.014177 0.230411 467 | 15513520437979 -0.226459 0.277454 0.026044 0.933304 -0.278168 0.015809 0.234167 468 | 15513570397622 -0.224437 0.276844 0.025767 0.933981 -0.283946 0.019274 0.238130 469 | 15513603722817 -0.223857 0.278213 0.024963 0.933736 -0.286525 0.021522 0.241700 470 | 15513637012379 -0.222761 0.279056 0.022921 0.933799 -0.288780 0.024647 0.245338 471 | 15513687000172 -0.223124 0.278555 0.018597 0.933958 -0.291252 0.028047 0.249689 472 | 15513720294850 -0.225356 0.277538 0.014820 0.933792 -0.292304 0.030032 0.252148 473 | 15513770239262 -0.230260 0.272819 0.009647 0.934054 -0.293165 0.033286 0.253973 474 | 15513803564457 -0.232780 0.268087 0.007240 0.934821 -0.292169 0.034479 0.254867 475 | 15513853492530 -0.234603 0.265105 0.003320 0.935238 -0.288663 0.035613 0.254441 476 | 15513886817726 -0.235937 0.265548 0.001317 0.934782 -0.284380 0.035246 0.252707 477 | 15513936808946 -0.239469 0.264788 0.000488 0.934099 -0.277191 0.033533 0.247473 478 | 15513970103624 -0.241714 0.262811 0.000477 0.934080 -0.271220 0.031557 0.242962 479 | 15514020091417 -0.243079 0.260517 -0.000928 0.934367 -0.260823 0.028095 0.235397 480 | 15514070082169 -0.245019 0.257475 -0.002327 0.934701 -0.249243 0.024563 0.225987 481 | 15514103376846 -0.243471 0.258147 -0.004399 0.934913 -0.240383 0.021937 0.220019 482 | 15514136727735 -0.241575 0.260109 -0.005957 0.934852 -0.231352 0.019741 0.213192 483 | 15514186685010 -0.240550 0.258589 -0.004819 0.935545 -0.217423 0.016645 0.203098 484 | 15514220010205 -0.236636 0.255856 -0.003262 0.937299 -0.207654 0.015160 0.196454 485 | 15514253283267 -0.234658 0.255454 -0.000914 0.937912 -0.197014 0.015073 0.190036 486 | 15514303271060 -0.228916 0.253511 -0.000312 0.939856 -0.181330 0.014032 0.180402 487 | 15514336573469 -0.226863 0.251436 0.000339 0.940911 -0.171308 0.014042 0.173081 488 | 15514369898665 -0.226171 0.249126 0.001260 0.941691 -0.160630 0.012697 0.165857 489 | 15514403193342 -0.224068 0.247760 0.001012 0.942554 -0.149589 0.010705 0.158475 490 | 15514453212958 -0.222615 0.244307 0.000525 0.943799 -0.133721 0.007545 0.146148 491 | 15514503200751 -0.220343 0.231385 -0.000559 0.947581 -0.118032 0.002024 0.133705 492 | 15514536387612 -0.219137 0.225227 -0.001468 0.949342 -0.107923 -0.001273 0.124499 493 | 15514586344887 -0.220065 0.220356 -0.001997 0.950269 -0.091857 -0.007015 0.112861 494 | 15514636207817 -0.218129 0.215345 -0.000933 0.951864 -0.076689 -0.011603 0.101494 495 | 15514669502495 -0.217354 0.213862 0.000646 0.952376 -0.066244 -0.014431 0.094462 496 | 15514702827690 -0.216164 0.211744 0.002899 0.953115 -0.055754 -0.016795 0.087869 497 | 15514752880750 -0.213077 0.205389 0.007762 0.955172 -0.039978 -0.018196 0.080017 498 | 15514802838025 -0.208456 0.206157 0.010987 0.955994 -0.024546 -0.020053 0.072202 499 | 15514836117031 -0.204346 0.207721 0.012172 0.956528 -0.014757 -0.020444 0.066602 500 | 15514886104824 -0.200039 0.204653 0.013453 0.958082 -0.000602 -0.020889 0.059362 501 | 15514936090653 -0.196147 0.198141 0.014532 0.960237 0.012499 -0.019905 0.052515 502 | 15514969415848 -0.193945 0.192584 0.015330 0.961801 0.021787 -0.019331 0.048278 503 | 15515002710526 -0.190673 0.187377 0.015482 0.963480 0.031210 -0.018649 0.044336 504 | 15515052711189 -0.187791 0.180291 0.016701 0.965376 0.044740 -0.016483 0.038464 505 | 15515102698982 -0.183844 0.172221 0.018934 0.967565 0.060075 -0.015868 0.032642 506 | 15515135973922 -0.180686 0.165038 0.021381 0.969359 0.069401 -0.015117 0.028047 507 | 15515185992233 -0.178464 0.145993 0.027994 0.972652 0.084935 -0.015477 0.021519 508 | 15515235931040 -0.179392 0.131702 0.030875 0.974433 0.100364 -0.015425 0.015370 509 | 15515285888315 -0.183952 0.120030 0.031495 0.975070 0.116511 -0.016331 0.009585 510 | 15515319218818 -0.184496 0.113205 0.031777 0.975775 0.126256 -0.016709 0.004736 511 | 15515369176093 -0.182583 0.106409 0.032022 0.976891 0.142194 -0.016547 0.000310 512 | 15515402501288 -0.179637 0.104496 0.032704 0.977620 0.152926 -0.016212 -0.002398 513 | 15515452390308 -0.176100 0.103761 0.034927 0.978265 0.168093 -0.014559 -0.007101 514 | 15515485715503 -0.174761 0.101682 0.037058 0.978645 0.178390 -0.013408 -0.009334 515 | 15515535730428 -0.173010 0.097111 0.039510 0.979324 0.192921 -0.010867 -0.013310 516 | 15515585687703 -0.170837 0.095171 0.038409 0.979940 0.207226 -0.007893 -0.015197 517 | 15515635644979 -0.170344 0.090804 0.036192 0.980524 0.220031 -0.003862 -0.017866 518 | 15515685704551 -0.173753 0.089356 0.033121 0.980168 0.234174 -0.000930 -0.019872 519 | 15515719029747 -0.175513 0.082457 0.031776 0.980503 0.243946 0.000330 -0.021597 520 | 15515769030677 -0.173200 0.062415 0.033063 0.982351 0.258666 0.001425 -0.024845 521 | 15515802355872 -0.171235 0.049735 0.034446 0.983371 0.268540 0.001162 -0.026747 522 | 15515852502413 -0.174358 0.034911 0.035143 0.983436 0.282775 0.001147 -0.031227 523 | 15515885797091 -0.176157 0.029404 0.034578 0.983315 0.292409 0.001134 -0.033296 524 | 15515952320543 -0.181850 0.020250 0.033327 0.982553 0.310600 0.002682 -0.038302 525 | 15515985645738 -0.182496 0.016127 0.032330 0.982542 0.319539 0.003719 -0.040433 526 | 15516035680292 -0.182172 0.009906 0.032778 0.982670 0.332020 0.005955 -0.043856 527 | 15516069005488 -0.180564 0.004246 0.033146 0.982995 0.340366 0.007537 -0.045573 528 | 15516118962763 -0.176743 -0.003999 0.034023 0.983661 0.352380 0.010335 -0.047262 529 | 15516152253176 -0.172002 -0.006658 0.035000 0.984452 0.359813 0.012912 -0.048506 530 | 15516202240969 -0.169325 -0.011485 0.036384 0.984821 0.371349 0.016085 -0.049231 531 | 15516252081517 -0.167186 -0.010581 0.035083 0.985244 0.383428 0.019459 -0.048586 532 | 15516285376195 -0.165576 -0.008031 0.033958 0.985579 0.391135 0.021541 -0.047930 533 | 15516335376616 -0.162765 -0.005067 0.032165 0.986127 0.401567 0.024888 -0.047255 534 | 15516385333892 -0.156854 -0.002285 0.030005 0.987163 0.411688 0.027363 -0.045328 535 | 15516418628570 -0.154690 0.000285 0.029149 0.987533 0.418003 0.028528 -0.043869 536 | 15516451938705 -0.152988 0.004183 0.028894 0.987797 0.423066 0.030587 -0.043355 537 | 15516501895980 -0.149878 0.015979 0.028398 0.988167 0.429533 0.032944 -0.039510 538 | 15516535169164 -0.149236 0.020652 0.028417 0.988177 0.432075 0.034821 -0.037467 539 | 15516585126439 -0.148525 0.021833 0.028177 0.988266 0.434339 0.036547 -0.033986 540 | 15516635067885 -0.143885 0.019593 0.026292 0.989051 0.432857 0.037324 -0.032253 541 | 15516685055678 -0.138409 0.009776 0.024924 0.990013 0.428346 0.035623 -0.032398 542 | 15516734916920 -0.141450 -0.000620 0.025784 0.989609 0.421449 0.033108 -0.035818 543 | 15516768211598 -0.147070 -0.000906 0.027028 0.988756 0.415041 0.030461 -0.037259 544 | 15516818199391 -0.152993 0.005583 0.028625 0.987797 0.403008 0.026043 -0.038348 545 | 15516851351772 -0.157123 0.005134 0.031367 0.987067 0.393274 0.023722 -0.039478 546 | 15516901370082 -0.156925 0.002717 0.037826 0.986882 0.376557 0.018782 -0.039567 547 | 15516934643130 -0.156201 0.002192 0.043402 0.986769 0.363910 0.016018 -0.039821 548 | 15516984600405 -0.160596 0.015142 0.050050 0.985634 0.344069 0.011110 -0.038609 549 | 15517017925601 -0.161163 0.025697 0.051824 0.985231 0.329829 0.008150 -0.037817 550 | 15517067784722 -0.162524 0.035720 0.052772 0.984645 0.306829 0.005678 -0.037999 551 | 15517101079400 -0.162955 0.042529 0.052978 0.984292 0.291132 0.004187 -0.037217 552 | 15517151118723 -0.156371 0.048589 0.051300 0.985168 0.266612 0.003756 -0.036747 553 | 15517184443918 -0.155382 0.053522 0.050273 0.985122 0.250064 0.003408 -0.035942 554 | 15517234290068 -0.155861 0.063006 0.047191 0.984637 0.225019 0.003248 -0.033523 555 | 15517284277861 -0.155565 0.068155 0.042810 0.984541 0.200225 0.003507 -0.030865 556 | 15517317572539 -0.155382 0.074103 0.039457 0.984281 0.183956 0.003798 -0.028173 557 | 15517350925003 -0.154934 0.077035 0.036177 0.984252 0.167625 0.005173 -0.025974 558 | 15517400882279 -0.153936 0.081343 0.032500 0.984190 0.143927 0.006157 -0.021169 559 | 15517434189525 -0.153413 0.086972 0.029752 0.983877 0.128543 0.007698 -0.017613 560 | 15517484177318 -0.155761 0.093318 0.025516 0.983046 0.106045 0.008523 -0.011452 561 | 15517517502513 -0.158206 0.098928 0.022370 0.982183 0.091632 0.008698 -0.006953 562 | 15517567545560 -0.162642 0.108347 0.016708 0.980576 0.070759 0.009405 0.000516 563 | 15517600840238 -0.162039 0.112628 0.012193 0.980260 0.057164 0.009292 0.005430 564 | 15517650700565 -0.162739 0.122392 0.007301 0.979021 0.037120 0.009153 0.013707 565 | 15517700688358 -0.163809 0.132125 0.004001 0.977596 0.017722 0.008351 0.022345 566 | 15517734008080 -0.166333 0.138329 0.002493 0.976316 0.005177 0.008364 0.027828 567 | 15517783995873 -0.170961 0.151964 0.001988 0.973486 -0.012409 0.007972 0.036543 568 | 15517834068010 -0.175001 0.157205 0.004074 0.971928 -0.030326 0.007883 0.044605 569 | 15517867362687 -0.177053 0.162265 0.005744 0.970716 -0.041249 0.007274 0.050282 570 | 15517900687883 -0.179137 0.166170 0.007519 0.969660 -0.051899 0.006252 0.056203 571 | 15517950703409 -0.186667 0.169631 0.010995 0.967605 -0.067926 0.004527 0.063910 572 | 15518000660684 -0.195552 0.179196 0.015392 0.964060 -0.082204 0.001646 0.075081 573 | 15518050680531 -0.202924 0.186539 0.020452 0.961044 -0.096219 -0.001340 0.086492 574 | 15518100637806 -0.204881 0.187493 0.022965 0.960387 -0.111197 -0.004732 0.098567 575 | 15518133915783 -0.207499 0.187719 0.024986 0.959730 -0.121214 -0.006269 0.105258 576 | 15518183903576 -0.210544 0.189598 0.026595 0.958653 -0.135744 -0.008394 0.116002 577 | 15518233851815 -0.212933 0.194073 0.028355 0.957179 -0.149934 -0.009547 0.125192 578 | 15518267177011 -0.214318 0.197074 0.030259 0.956198 -0.159078 -0.009933 0.131755 579 | 15518317164804 -0.215574 0.202410 0.032548 0.954724 -0.172311 -0.010052 0.141369 580 | 15518367179603 -0.216436 0.208094 0.034085 0.953253 -0.186189 -0.009142 0.148907 581 | 15518400504798 -0.217130 0.211300 0.035047 0.952354 -0.194954 -0.008427 0.154553 582 | 15518450427955 -0.217243 0.216500 0.036172 0.951118 -0.207987 -0.006376 0.161641 583 | 15518483722633 -0.217695 0.218544 0.036767 0.950524 -0.217059 -0.005322 0.166833 584 | 15518533752509 -0.216013 0.220640 0.036263 0.950443 -0.231220 -0.003297 0.173721 585 | 15518567047186 -0.215397 0.221466 0.036078 0.950397 -0.240354 -0.001932 0.178792 586 | 15518617034979 -0.213304 0.222825 0.033513 0.950646 -0.253962 0.000327 0.186631 587 | 15518650279928 -0.211332 0.222502 0.030239 0.951271 -0.263509 0.002470 0.190343 588 | 15518700237203 -0.212227 0.225655 0.025418 0.950470 -0.276534 0.005269 0.197407 589 | 15518733567341 -0.212924 0.229904 0.022376 0.949372 -0.284915 0.008056 0.200460 590 | 15518783524617 -0.215473 0.238127 0.019214 0.946836 -0.296660 0.011579 0.206847 591 | 15518816807130 -0.216146 0.242592 0.018011 0.945572 -0.304057 0.014603 0.210863 592 | 15518866794923 -0.216655 0.246347 0.016949 0.944503 -0.314617 0.018549 0.217465 593 | 15518916782716 -0.214662 0.248432 0.014391 0.944454 -0.324098 0.022329 0.224565 594 | 15518950153989 -0.214742 0.250566 0.012340 0.943902 -0.330329 0.025384 0.228407 595 | 15518983448666 -0.213554 0.250616 0.009808 0.944188 -0.336056 0.028129 0.232673 596 | 15519016697687 -0.213148 0.250758 0.006979 0.944266 -0.341222 0.031510 0.236074 597 | 15519066685480 -0.213570 0.251402 0.003167 0.944021 -0.348585 0.035471 0.241887 598 | 15519099980158 -0.210925 0.245229 0.000248 0.946241 -0.353527 0.037318 0.245232 599 | 15519133285430 -0.209832 0.239282 -0.002519 0.948002 -0.357707 0.038738 0.247409 600 | 15519166580108 -0.210986 0.236067 -0.004603 0.948544 -0.360273 0.038904 0.249906 601 | 15519216598418 -0.208514 0.231828 -0.008517 0.950108 -0.360575 0.038067 0.252329 602 | 15519249897580 -0.208676 0.230781 -0.010234 0.950310 -0.358934 0.037442 0.251111 603 | 15519283192258 -0.210110 0.230767 -0.011328 0.949985 -0.356002 0.035468 0.249391 604 | 15519316517453 -0.211535 0.230252 -0.011539 0.949791 -0.352042 0.033074 0.246351 605 | 15519366443775 -0.213471 0.226965 -0.009990 0.950167 -0.344554 0.030385 0.241102 606 | 15519416431568 -0.215809 0.223565 -0.009405 0.950451 -0.332984 0.026079 0.232082 607 | 15519449684456 -0.216368 0.219911 -0.009756 0.951172 -0.324071 0.023511 0.224580 608 | 15519483040169 -0.218190 0.220342 -0.010465 0.950649 -0.313393 0.019958 0.216207 609 | 15519532997445 -0.221928 0.223041 -0.009529 0.949163 -0.294991 0.014597 0.203957 610 | 15519582989432 -0.217426 0.213163 -0.005557 0.952500 -0.276192 0.010291 0.191377 611 | 15519616314627 -0.216199 0.212412 -0.002602 0.952960 -0.262535 0.007551 0.183599 612 | 15519649647047 -0.213246 0.211500 -0.000051 0.953831 -0.248380 0.005844 0.176157 613 | 15519699634840 -0.206997 0.206394 0.005095 0.956309 -0.227750 0.002674 0.164956 614 | 15519732914683 -0.205630 0.208514 0.008379 0.956121 -0.213194 0.002275 0.159364 615 | 15519782902476 -0.200335 0.211053 0.011821 0.956652 -0.192347 0.000679 0.149817 616 | 15519816227671 -0.198408 0.213269 0.015037 0.956517 -0.178404 -0.000278 0.143817 617 | 15519849674721 -0.199515 0.209162 0.020376 0.957094 -0.165625 -0.000299 0.138070 618 | 15519899662514 -0.196308 0.201235 0.020620 0.959449 -0.145649 -0.002565 0.128735 619 | 15519932909564 -0.193516 0.199218 0.017766 0.960494 -0.132285 -0.003363 0.123082 620 | 15519982866840 -0.194789 0.188999 0.018174 0.962292 -0.111198 -0.006064 0.114121 621 | 15520016192035 -0.196322 0.184792 0.018661 0.962788 -0.096644 -0.007897 0.108776 622 | 15520066110653 -0.196008 0.183891 0.017965 0.963038 -0.074822 -0.008956 0.101363 623 | 15520099435848 -0.195373 0.180412 0.018785 0.963809 -0.060079 -0.010036 0.097250 624 | 15520149353617 -0.192491 0.169928 0.020810 0.966250 -0.039609 -0.010395 0.090971 625 | 15520182648295 -0.190220 0.163706 0.021946 0.967747 -0.026233 -0.010781 0.087630 626 | 15520216041251 -0.188499 0.159187 0.022042 0.968835 -0.013873 -0.010191 0.084162 627 | 15520265998526 -0.185691 0.152524 0.020960 0.970472 0.004056 -0.009959 0.080238 628 | 15520299323722 -0.184208 0.147690 0.019747 0.971527 0.015563 -0.009678 0.078033 629 | 15520349385935 -0.180816 0.137209 0.017438 0.973743 0.031491 -0.008172 0.074116 630 | 15520382680612 -0.177943 0.131050 0.014782 0.975163 0.042212 -0.007512 0.072182 631 | 15520432612231 -0.175906 0.124123 0.010474 0.976494 0.058038 -0.005209 0.069098 632 | 15520465906909 -0.175321 0.120392 0.007516 0.977093 0.067727 -0.004431 0.067200 633 | 15520499232104 -0.174296 0.117396 0.004832 0.977658 0.076678 -0.003743 0.065403 634 | 15520549151880 -0.172000 0.112420 0.002773 0.978657 0.088463 -0.001587 0.062391 635 | 15520582446557 -0.172257 0.108099 0.002672 0.979099 0.095560 -0.000526 0.060811 636 | 15520615802270 -0.171643 0.104723 0.002045 0.979575 0.101981 0.000803 0.059723 637 | 15520665760843 -0.170735 0.100063 0.000017 0.980223 0.110157 0.004164 0.057037 638 | 15520699086038 -0.172005 0.098280 -0.001082 0.980181 0.114801 0.005976 0.056385 639 | 15520732383069 -0.172579 0.097942 -0.002733 0.980110 0.118859 0.008264 0.055372 640 | 15520782340344 -0.171426 0.094244 -0.005455 0.980664 0.123435 0.010753 0.055085 641 | 15520832326648 -0.170256 0.090965 -0.006217 0.981173 0.126109 0.013697 0.054974 642 | 15520865621326 -0.169717 0.090939 -0.005860 0.981271 0.127702 0.014713 0.055099 643 | 15520915639636 -0.165199 0.090848 -0.003811 0.982060 0.129338 0.015775 0.055694 644 | 15520965590813 -0.163360 0.097407 -0.001658 0.981745 0.130145 0.017761 0.056449 645 | 15521015548088 -0.162163 0.106599 0.000039 0.980989 0.130526 0.018602 0.057837 646 | 15521065455864 -0.160999 0.114723 0.000667 0.980264 0.129692 0.019962 0.058099 647 | 15521098781059 -0.161462 0.117860 0.000912 0.979815 0.129432 0.019977 0.058546 648 | 15521148781864 -0.162548 0.121773 0.001071 0.979157 0.128383 0.020514 0.058171 649 | 15521198769657 -0.163221 0.125148 0.001252 0.978619 0.127580 0.019919 0.058648 650 | 15521232133266 -0.164223 0.126415 0.001564 0.978288 0.126964 0.020225 0.058637 651 | 15521282090541 -0.164172 0.126173 0.002489 0.978326 0.126137 0.019828 0.059087 652 | 15521315415736 -0.164264 0.126057 0.003063 0.978324 0.125613 0.019508 0.059535 653 | 15521365441870 -0.164692 0.126006 0.003754 0.978256 0.124728 0.020129 0.059854 654 | 15521415399145 -0.165359 0.125588 0.004315 0.978195 0.123954 0.019756 0.060182 655 | 15521465395462 -0.167377 0.126215 0.004787 0.977769 0.123057 0.020012 0.059110 656 | 15521515383255 -0.170182 0.128791 0.005080 0.976947 0.122657 0.019105 0.058919 657 | 15521565336432 -0.171870 0.129843 0.005310 0.976511 0.122121 0.019044 0.057776 658 | 15521615294055 -0.172693 0.130250 0.005260 0.976312 0.121935 0.019017 0.057152 659 | 15521665281848 -0.172195 0.131575 0.004796 0.976224 0.122060 0.018957 0.057401 660 | 15521698576525 -0.172067 0.132549 0.004799 0.976115 0.122198 0.018890 0.057787 661 | 15521748549095 -0.171246 0.133122 0.004647 0.976182 0.122300 0.019221 0.057754 662 | 15521798536888 -0.171124 0.134072 0.004307 0.976075 0.122555 0.019030 0.058278 663 | 15521848488672 -0.171074 0.134648 0.003999 0.976006 0.122521 0.019254 0.058007 664 | 15521898445947 -0.171084 0.135609 0.003323 0.975874 0.122745 0.018894 0.058444 665 | 15521948434283 -0.170878 0.137324 0.002364 0.975673 0.122727 0.019224 0.058214 666 | 15521981728961 -0.171246 0.138263 0.001811 0.975477 0.122870 0.019174 0.058438 667 | 15522031686236 -0.169763 0.139354 0.000558 0.975582 0.123165 0.019062 0.059070 668 | -------------------------------------------------------------------------------- /Visualization/FigureRotator.m: -------------------------------------------------------------------------------- 1 | classdef FigureRotator < handle 2 | 3 | % FigureRotator 4 | % 5 | % Apply this to a figure to use a mouse to rotate around a target position 6 | % and zoom in and out. This is similar to the Rotate 3D capability found in 7 | % the standard figure menu, but this allows greater flexibility and 8 | % fluidity of movement. An example is provided in example_figure_rotator.m, 9 | % and additional examples are provided below. 10 | % 11 | % Left-click and drag to rotate about the target. 12 | % Scroll the mouse wheel to move towards/away from the target. 13 | % Right-click and drag to zoom the camera in/out, changing the view angle. 14 | % Double-click to reset the "up" vector. 15 | % Press 'r' to reset the view to what it was when the FigureRotator started. 16 | % 17 | % Example: 18 | % 19 | % figure(); 20 | % plot3(randn(1, 10), randn(1, 10), randn(1, 10)); 21 | % drawnow(); 22 | % f = FigureRotator(gca); 23 | % 24 | % The FigureRotator can later be stopped by calling the Stop() function. 25 | % 26 | % f.Stop(); 27 | % 28 | % It's often helpful to specify the initial camera parameters, like 29 | % position, target, up vector, and view angle. These can all be passed to 30 | % the constructor. (These are all properties of axes objects in MATLAB. See 31 | % 'axes properties' in the documentation for more.) 32 | % 33 | % f = FigureRotator(gca, 'CameraTarget', [0 0 0], ... 34 | % 'CameraPosition', [15 0 0], ... 35 | % 'CameraUpVector', [0 0 1], ... 36 | % 'CameraViewAngle', 60); 37 | % 38 | % The FigureRotator allows complete 3D rotation, so if you start losing 39 | % track of "up", you can always re-align the camera's up vector with the 40 | % axes' [0 0 1] by calling RestoreUp(). 41 | % 42 | % You can also set the up vector with SetUpVector(). This take two arguments. 43 | % The first is the 3D up vector, e.g., [0 0 1]. The second says whether "up" 44 | % should always remain "up" (constrainted rotation). 45 | % 46 | % % Look at peaks sideways. 47 | % peaks(); 48 | % f = FigureRotator(); 49 | % f.SetUpVector([0 1 0]); 50 | % 51 | % Now try rotating and then double-clicking. Note how double-clicking realigns 52 | % the y axis with up. 53 | % 54 | % Now try: 55 | % 56 | % f.SetUpVector([0 1 0], true); % Keep "up" pointing up. 57 | % 58 | % Note how it now rotates about the y axis. 59 | % 60 | % This object uses the figure's WindowButtonUpFcn, WindowButtonDownFcn, 61 | % WindowButtonMotionFcn, WindowScrollWheelFcn, and KeyPressFcn callbacks. If 62 | % those are necessary for other tasks as well, callbacks can be attached to the 63 | % FigureRotator, which will pass all arguments on to the provided callback 64 | % function. 65 | % 66 | % Example: 67 | % 68 | % f = FigureRotator(gca); 69 | % f.AttachCallback('WindowButtonDownFcn', 'disp(''clicked'');'); 70 | % 71 | % Or multiple callbacks can be set with a single call: 72 | % 73 | % f.AttachCallback('WindowButtonDownFcn', 'disp(''down'');', ... 74 | % 'WindowButtonUpFcn', 'disp(''up'');', ... 75 | % 'WindowButtonMotionFcn', 'disp(''moving'');', ... 76 | % 'WindowScrollWheelFcn', @(~, ~) disp('scrolling'), ... 77 | % 'KeyPressFcn', 'disp(''key'');'); 78 | % 79 | % A single FigureRotator can control multiple axes, even axes across 80 | % multiple figures. 81 | % 82 | % Example: 83 | % 84 | % figure(1); 85 | % clf(); 86 | % ha1 = subplot(2, 1, 1); 87 | % peaks; 88 | % ha2 = subplot(2, 1, 2); 89 | % peaks; 90 | % 91 | % figure(2); 92 | % clf(); 93 | % peaks; 94 | % ha3 = gca(); 95 | % 96 | % f = FigureRotator([ha1 ha2 ha3]); 97 | % 98 | % The FigureRotator now controls all three figures -- useful for keeping the 99 | % same perspective across multiple objects. 100 | % 101 | % --- Change Log --- 102 | % 103 | % 2014-09-11: Updated for R2014B graphics. Change copyright 2014 104 | % Tucker McClure. 105 | % 106 | % 2014-03-26: Allows view to be reset with 'r', has improved handling of 107 | % callbacks for multiple axes, more examples of using up vector, and more 108 | % comments. Change copyright 2014 Tucker McClure. 109 | % 110 | % Original. Copyrirght 2012, The MathWorks, Inc. 111 | % 112 | % --- 113 | % 114 | % Copyright 2014, The MathWorks, Inc. and Tucker McClure 115 | 116 | properties 117 | 118 | % Figure and axes handles 119 | h_f; 120 | h_a; 121 | 122 | % Current states 123 | rotating = false; 124 | zooming = false; 125 | 126 | % Mouse positions 127 | rotate_start_point = [0 0]; % Mouse position on button down 128 | zoom_start_point = [0 0]; % Mouse position on button down 129 | 130 | wbdf; % Pass-through WindowButtonDownFcn 131 | wbuf; % Pass-through WindowButtonUpFcn 132 | wbmf; % Pass-through WindowButtonMotionFcn 133 | wswf; % Pass-through WindowScrollWheelFcn 134 | kpf; % Pass-through KeyPressFcn 135 | 136 | keep_up = false; % True iff we should always restore up after movement 137 | up = [0; 0; 1]; % Up vector 138 | 139 | is_stopped = false; % True iff we should no longer control the axes. 140 | 141 | % Original states 142 | original; 143 | 144 | end 145 | 146 | methods 147 | 148 | % Construct a FigureRotator for the given axes. 149 | function o = FigureRotator(axes_handle, varargin) 150 | 151 | % Use gca if none is given. 152 | if nargin == 0 153 | axes_handle = gca(); 154 | end 155 | 156 | % Record the axes and figure. 157 | o.h_a = axes_handle; 158 | o.h_f = get(axes_handle, 'Parent'); 159 | if iscell(o.h_f) 160 | o.h_f = [o.h_f{:}]; 161 | end 162 | 163 | % Pass any arguments on to the axes object. 164 | if nargin >= 2 165 | set(o.h_a, varargin{:}); 166 | end 167 | 168 | % Get the original view positions. 169 | o.original.position = get(o.h_a, 'CameraPosition'); 170 | o.original.target = get(o.h_a, 'CameraTarget'); 171 | o.original.up = get(o.h_a, 'CameraUpVector'); 172 | o.original.view_angle = get(o.h_a, 'CameraViewAngle'); 173 | 174 | % Save the original up vector as the current up vector. 175 | o.up = o.original.up; 176 | 177 | % Set the figure callbacks to register with this object. 178 | set(o.h_f, ... 179 | 'WindowButtonDownFcn', @o.ButtonDown, ... 180 | 'WindowButtonUpFcn', @o.ButtonUp, ... 181 | 'WindowButtonMotionFcn', @o.Move, ... 182 | 'WindowScrollWheelFcn', @o.Wheel, ... 183 | 'KeyPressFcn', @o.Key); 184 | 185 | % Set up the axes object for what we need. We get the last 186 | % word. 187 | set(o.h_a, ... 188 | 'CameraPositionMode', 'manual', ... 189 | 'CameraTargetMode', 'manual', ... 190 | 'CameraUpVectorMode', 'manual', ... 191 | 'CameraViewAngleMode', 'manual', ... 192 | 'XLimMode', 'manual', ... 193 | 'YLimMode', 'manual', ... 194 | 'ZLimMode', 'manual', ... 195 | 'DataAspectRatioMode', 'manual'); 196 | 197 | end 198 | 199 | % Called when a user clicks 200 | function ButtonDown(o, h, event, varargin) 201 | 202 | % If the user is clicking in the figure, but not on one of our axes, 203 | % ignore it. 204 | if ~any(ishandle(o.h_a)) || ~any(gca() == o.h_a) 205 | return; 206 | end 207 | 208 | % Get the button type. 209 | switch get(h, 'SelectionType') 210 | 211 | % Rotate around. 212 | case {'normal', 'extend'} 213 | 214 | % Record the starting point and that we're rotating. 215 | o.rotate_start_point = get(h, 'CurrentPoint'); 216 | o.rotating = true; 217 | 218 | % Zoom. 219 | case 'alt' 220 | 221 | % Record the starting point and that we're zooming. 222 | o.zoom_start_point = get(h, 'CurrentPoint'); 223 | o.zooming = true; 224 | 225 | % When double-clicking, restore up. 226 | case 'open' 227 | 228 | o.RestoreUp(); 229 | 230 | end 231 | 232 | % If there's a callback attachment, execute it. 233 | execute_callback(o.wbdf, h, event, varargin{:}); 234 | 235 | end 236 | 237 | % Called when user releases a click 238 | function ButtonUp(o, h, event, varargin) 239 | 240 | % If the user is clicking in the figure, but not on one of our axes, 241 | % ignore it. 242 | if ~any(ishandle(o.h_a)) || ~any(gca() == o.h_a) 243 | return; 244 | end 245 | 246 | % Get the button type. 247 | switch get(h, 'SelectionType') 248 | 249 | % Stop rotating. 250 | case {'normal', 'extend'} 251 | o.rotating = false; 252 | 253 | % Stop zooming. 254 | case 'alt' 255 | o.zooming = false; 256 | 257 | end 258 | 259 | % If there's a callback attachment, execute it. 260 | execute_callback(o.wbuf, h, event, varargin{:}); 261 | 262 | end 263 | 264 | % Called when mouse moves in figure 265 | function Move(o, h, event, varargin) 266 | 267 | % If the user is clicking in the figure, but not on one of our axes, 268 | % ignore it. 269 | if ~any(ishandle(o.h_a)) || ~any(gca() == o.h_a) 270 | return; 271 | end 272 | 273 | if o.rotating 274 | 275 | % Get the mouse position in the window. 276 | s = feval(@(x) x(3:4), get(h, 'Position')); 277 | p = get(h, 'CurrentPoint'); 278 | r = (p - o.rotate_start_point)./s; 279 | 280 | % Get the current state wrt the target and frame. 281 | dar = get(gca(), 'DataAspectRatio')'; 282 | r_t0 = get(gca(), 'CameraTarget')' ./ dar; 283 | r_c0 = get(gca(), 'CameraPosition')' ./ dar; 284 | up_hat = get(gca(), 'CameraUpVector')' ./ dar; 285 | r_tc = r_t0 - r_c0; 286 | r_tc_hat = normalize(r_tc); 287 | 288 | % Correct up. 289 | up_hat = normalize(up_hat - r_tc_hat'*up_hat*r_tc_hat); 290 | 291 | % Find "right" (this will be a unit vector since r_tc_hat 292 | % and up_hat are orthonormal). 293 | right_hat = cross(r_tc_hat, up_hat); 294 | 295 | % Calculate where the mouse is in the axes space from its 296 | % location in the 2D figure window. 297 | r_mc = r(2) * up_hat + r(1) * right_hat; 298 | 299 | % Calculate the rotation axis. 300 | a_hat = normalize(cross(r_tc - r_mc, r_tc)); 301 | 302 | % Calculate the rotation matrix. 303 | Q = aa2dcm(a_hat, norm(r_mc)*pi); 304 | 305 | % Calculate the new camera position, accounting for 306 | % non-equal aspect ratios. 307 | r_n0 = -Q*r_tc + r_t0; 308 | 309 | % Update the relevant quantities. 310 | for k = 1:length(o.h_a) 311 | if ishandle(o.h_a(k)) 312 | set(o.h_a(k), 'CameraPosition', r_n0 .* dar, ... 313 | 'CameraUpVector', Q*up_hat .* dar); 314 | end 315 | end 316 | 317 | % If up should stay up at all time, restore it. 318 | if o.keep_up 319 | o.RestoreUp(); 320 | end 321 | 322 | % Update the "last" accounted point. 323 | o.rotate_start_point = p; 324 | 325 | end 326 | 327 | if o.zooming 328 | 329 | % Get the starting view angle. 330 | view_angle = get(gca(), 'CameraViewAngle'); 331 | 332 | % Get the mouse position in the window. 333 | s = feval(@(x) x(4), get(h, 'Position')); 334 | p = get(h, 'CurrentPoint'); 335 | r = -(p(2) - o.zoom_start_point(2))/s; 336 | new_view_angle = min(2^r*view_angle, 180-eps); 337 | 338 | for k = 1:length(o.h_a) 339 | if ishandle(o.h_a(k)) 340 | set(o.h_a(k), 'CameraViewAngle', new_view_angle); 341 | end 342 | end 343 | 344 | % Update the "last" accounted point. 345 | o.zoom_start_point = p; 346 | 347 | end 348 | 349 | % If there's a callback attachment, execute it. 350 | execute_callback(o.wbmf, h, event, varargin{:}); 351 | 352 | end 353 | 354 | % Called for scroll wheel 355 | function Wheel(o, h, event, varargin) 356 | 357 | % If the user is clicking in the figure, but not on one of our axes, 358 | % ignore it. 359 | if ~any(ishandle(o.h_a)) || ~any(gca() == o.h_a) 360 | return; 361 | end 362 | 363 | % Scalar to increase/decrease distance to target. 364 | s = 1.2^double(event.VerticalScrollCount); 365 | 366 | % Update what we're currently seeing by the appropriate amount. 367 | t0 = get(gca(), 'CameraTarget'); 368 | c0 = get(gca(), 'CameraPosition'); 369 | r_n0 = s * (c0 - t0) + t0; 370 | 371 | for k = 1:length(o.h_a) 372 | if ishandle(o.h_a(k)) 373 | set(o.h_a(k), 'CameraPosition', r_n0); 374 | end 375 | end 376 | 377 | % If there's a callback attachment, execute it. 378 | execute_callback(o.wswf, h, event, varargin{:}); 379 | 380 | end 381 | 382 | % Called when a key is pressed in the figure. 383 | function Key(o, h, event, varargin) 384 | 385 | % If the user is clicking in the figure, but not on one of our axes, 386 | % ignore it. 387 | if ~any(ishandle(o.h_a)) || ~any(gca() == o.h_a) 388 | return; 389 | end 390 | 391 | % See which key. 392 | switch event.Key 393 | 394 | % If 'r', let's reset the view. 395 | case 'r' 396 | if length(o.h_a) == 1 397 | if ishandle(o.h_a) 398 | set(o.h_a, ... 399 | 'CameraTarget', o.original.target, ... 400 | 'CameraPosition', o.original.position, ... 401 | 'CameraUpVector', o.original.up, ... 402 | 'CameraViewAngle', o.original.view_angle); 403 | end 404 | else 405 | for k = 1:length(o.h_a) 406 | if ishandle(o.h_a(k)) 407 | set(o.h_a(k), ... 408 | 'CameraTarget', o.original.target{k}, ... 409 | 'CameraPosition', o.original.position{k}, ... 410 | 'CameraUpVector', o.original.up{k}, ... 411 | 'CameraViewAngle', o.original.view_angle{k}); 412 | end 413 | end 414 | end 415 | 416 | end 417 | 418 | % If there's a callback, execute it. 419 | execute_callback(o.kpf, h, event, varargin{:}); 420 | 421 | end 422 | 423 | % Sometime users like to return "up" to [0 0 1], so we'll give them 424 | % a function to call. 425 | function RestoreUp(o) 426 | if iscell(o.up) 427 | for k = 1:length(o.up) 428 | if ishandle(o.h_a(k)) 429 | set(o.h_a(k), 'CameraUpVector', o.up{k}); 430 | end 431 | end 432 | else 433 | for k = 1:length(o.h_a) 434 | if ishandle(o.h_a(k)) 435 | set(o.h_a(k), 'CameraUpVector', o.up); 436 | end 437 | end 438 | end 439 | end 440 | 441 | % Add a pass-through callback for one of the callbacks 442 | % FigureRotator hogs to itself. This way, a user can still get all 443 | % the info he needs from a figure's callbacks *and* use the 444 | % rotator. 445 | function AttachCallback(o, varargin) 446 | 447 | for k = 2:2:length(varargin) 448 | switch varargin{k-1} 449 | case 'WindowButtonDownFcn' 450 | o.wbdf = varargin{k}; 451 | case 'WindowButtonUpFcn' 452 | o.wbuf = varargin{k}; 453 | case 'WindowButtonMotionFcn' 454 | o.wbmf = varargin{k}; 455 | case 'WindowScrollWheelFcn' 456 | o.wswf = varargin{k}; 457 | case 'KeyPressFcn' 458 | o.kpf = varargin{k}; 459 | otherwise 460 | warning('Invalid callback attachment.'); 461 | end 462 | end 463 | 464 | end 465 | 466 | % Allow the user to specify that up should always be up and to 467 | % specify what up is. 468 | function SetUpVector(o, up, on) 469 | o.up = up; 470 | if nargin >= 3 471 | o.keep_up = logical(on); 472 | end 473 | o.RestoreUp(); 474 | end 475 | 476 | % We're done. Get rid of the callbacks. If there were pass-through 477 | % callbacks, replace our callbacks with those. 478 | function Stop(o) 479 | o.is_stopped = true; 480 | for k = 1:length(o.h_f) 481 | if ishandle(o.h_f(k)) 482 | set(o.h_f(k), ... 483 | 'WindowButtonDownFcn', o.wbdf, ... 484 | 'WindowButtonUpFcn', o.wbuf, ... 485 | 'WindowButtonMotionFcn', o.wbmf, ... 486 | 'WindowScrollWheelFcn', o.wswf, ... 487 | 'KeyPressFcn', o.kpf); 488 | end 489 | end 490 | end 491 | 492 | end 493 | 494 | end 495 | 496 | % Safely normalize an input vector. 497 | function x_hat = normalize(x) 498 | n = norm(x); 499 | if n > eps 500 | x_hat = x/n; 501 | else 502 | x_hat = x; 503 | end 504 | end 505 | 506 | % Convert the specified axis and angle of rotation to a direction cosine 507 | % matrix. 508 | function M = aa2dcm(ax, an) 509 | M = eye(3)*cos(an) + (1-cos(an))*(ax*ax') + crs(ax)*sin(an); 510 | end 511 | 512 | % Returns a skew-symmetric "cross product" matrix from 3-by-1 vector, v, 513 | % such that cross(v, b) == crs(v)*b. 514 | function M = crs(v) 515 | M = [ 0 -v(3) v(2); ... 516 | v(3) 0 -v(1); ... 517 | -v(2) v(1) 0]; 518 | end 519 | 520 | % Execute whatever callback was requested. 521 | function execute_callback(cb, h, event, varargin) 522 | 523 | % If there's anything here... 524 | if ~isempty(cb) 525 | 526 | % If might be a regular function handle. If so, just pass along the 527 | % handle and event. 528 | if isa(cb, 'function_handle') 529 | 530 | cb(h, event); 531 | 532 | % If it's a cell array, it should contain a function handle and 533 | % additional arguments. 534 | elseif iscell(cb) 535 | 536 | cb(h, event, varargin{:}); 537 | 538 | % Otherwise, if it's text, evaluate it. 539 | elseif ischar(cb) && ~isempty(cb) 540 | 541 | eval(cb); 542 | 543 | % Otherwise, we don't know what to do. 544 | else 545 | 546 | error('FigureRotator:InvalidCallback', ... 547 | 'Invalid figure callback in FigureRotator.'); 548 | 549 | end 550 | 551 | end 552 | 553 | end 554 | 555 | 556 | -------------------------------------------------------------------------------- /Visualization/angle2rotmtx.m: -------------------------------------------------------------------------------- 1 | function [ R ] = angle2rotmtx(eulerAngle) 2 | % Project: Patch-based Illumination invariant Visual Odometry (PIVO) 3 | % Function: angle2rotmtx 4 | % 5 | % Description: 6 | % This function return the rotation matrix rotMtx 7 | % [Body frame] = rotMtx * [Inertial frame] 8 | % from [phi;theta;psi] angle defined as ZYX sequence to rotation matrix 9 | % 10 | % Example: 11 | % OUTPUT: 12 | % R = rotation matrix (3x3) defined as [body frame] = R * [inertial frame] (R = R_bg) 13 | % 14 | % INPUT: 15 | % eulerAngle: angle vector composed of [phi;theta;psi] 16 | % phi = Rotation angle along x direction in radians 17 | % theta = Rotation angle along y direction in radians 18 | % psi = Rotation angle along z direction in radians 19 | % 20 | % NOTE: 21 | % 22 | % Author: Pyojin Kim 23 | % Email: pjinkim1215@gmail.com 24 | % Website: 25 | % 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | % log: 28 | % 2016-08-20: 29 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 30 | % 31 | 32 | % assign roll, pitch, yaw values 33 | phi = eulerAngle(1); 34 | theta = eulerAngle(2); 35 | psi = eulerAngle(3); 36 | 37 | R_x = [1 0 0;0 cos(phi) -sin(phi);0 sin(phi) cos(phi)]; 38 | 39 | R_y = [cos(theta) 0 sin(theta);0 1 0;-sin(theta) 0 cos(theta)]; 40 | 41 | R_z = [cos(psi) -sin(psi) 0; sin(psi) cos(psi) 0; 0 0 1]; 42 | 43 | rotMtxBody = [R_z * R_y * R_x]; % [Inertial frame] = rotMtxBody * [Body frame] 44 | 45 | R = rotMtxBody.'; % [Body frame] = rotMtx * [Inertial frame] 46 | 47 | end 48 | 49 | -------------------------------------------------------------------------------- /Visualization/main_script.m: -------------------------------------------------------------------------------- 1 | clc; 2 | close all; 3 | clear variables; %clear classes; 4 | rand('state',0); % rand('state',sum(100*clock)); 5 | dbstop if error; 6 | 7 | 8 | %% common setting to read text files 9 | 10 | delimiter = ' '; 11 | headerlinesIn = 1; 12 | nanoSecondToSecond = 1000000000; 13 | 14 | 15 | %% 1) parse ARCore sensor pose data 16 | 17 | % parsing ARCore sensor pose data text file 18 | textFileDir = 'ARCore_sensor_pose.txt'; 19 | textARCorePoseData = importdata(textFileDir, delimiter, headerlinesIn); 20 | ARCorePoseTime = textARCorePoseData.data(:,1).'; 21 | ARCorePoseTime = (ARCorePoseTime - ARCorePoseTime(1)) ./ nanoSecondToSecond; 22 | ARCorePoseRotation = textARCorePoseData.data(:,[5 2 3 4]).'; 23 | ARCorePoseTranslation = textARCorePoseData.data(:,[6 7 8]).'; 24 | 25 | % ARCore sensor pose with various 6-DoF sensor pose representations 26 | numPose = size(ARCorePoseRotation,2); 27 | R_gb_ARCore = zeros(3,3,numPose); 28 | T_gb_ARCore = cell(1,numPose); 29 | stateEsti_ARCore = zeros(6,numPose); 30 | for k = 1:numPose 31 | 32 | % rigid body transformation matrix (4x4) (rotation matrix SO(3) from quaternion) 33 | R_gb_ARCore(:,:,k) = q2r(ARCorePoseRotation(:,k)); 34 | T_gb_ARCore{k} = [R_gb_ARCore(:,:,k), ARCorePoseTranslation(:,k); [0, 0, 0, 1]]; 35 | 36 | % state vector and rotation matrix 37 | stateEsti_ARCore(1:3,k) = T_gb_ARCore{k}(1:3,4); 38 | [yaw, pitch, roll] = dcm2angle(R_gb_ARCore(:,:,k)); 39 | stateEsti_ARCore(4:6,k) = [roll; pitch; yaw]; 40 | end 41 | 42 | % plot update rate of ARCore sensor pose 43 | timeDifference = diff(ARCorePoseTime); 44 | meanUpdateRate = (1/mean(timeDifference)); 45 | figure; 46 | plot(ARCorePoseTime(2:end), timeDifference, 'm'); hold on; grid on; axis tight; 47 | set(gcf,'color','w'); hold off; 48 | axis([min(ARCorePoseTime) max(ARCorePoseTime) min(timeDifference) max(timeDifference)]); 49 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17); 50 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17); 51 | ylabel('Time Difference [sec]','FontName','Times New Roman','FontSize',17); 52 | title(['Mean Update Rate: ', num2str(meanUpdateRate), ' Hz'],'FontName','Times New Roman','FontSize',17); 53 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure 54 | 55 | 56 | %% 2) parse ARCore point cloud data 57 | 58 | % parsing ARCore point cloud data text file 59 | textFileDir = 'ARCore_point_cloud.txt'; 60 | textARCorePointData = importdata(textFileDir, delimiter, headerlinesIn); 61 | 62 | % ARCore 3D point cloud 63 | ARCorePoints = textARCorePointData.data(:,[1:3]).'; 64 | ARCoreColors = textARCorePointData.data(:,[4:6]).'; 65 | numPoints = size(ARCorePoints,2); 66 | 67 | 68 | %% plot ARCore VIO results 69 | 70 | % 1) play 3D trajectory of ARCore sensor pose 71 | figure(10); 72 | for k = 1:numPose 73 | figure(10); cla; 74 | 75 | % draw moving trajectory 76 | p_gb_ARCore = stateEsti_ARCore(1:3,1:k); 77 | plot3(p_gb_ARCore(1,:), p_gb_ARCore(2,:), p_gb_ARCore(3,:), 'm', 'LineWidth', 2); hold on; grid on; axis equal; 78 | 79 | % draw sensor body and frame 80 | plot_inertial_frame(0.5); 81 | Rgb_ARCore_current = T_gb_ARCore{k}(1:3,1:3); 82 | pgb_ARCore_current = T_gb_ARCore{k}(1:3,4); 83 | plot_sensor_ARCore_frame(Rgb_ARCore_current, pgb_ARCore_current, 0.5, 'm'); 84 | refresh; pause(0.01); k 85 | end 86 | 87 | 88 | % 2) plot ARCore VIO motion estimation results 89 | figure; 90 | h_ARCore = plot3(stateEsti_ARCore(1,:),stateEsti_ARCore(2,:),stateEsti_ARCore(3,:),'m','LineWidth',2); hold on; grid on; 91 | scatter3(ARCorePoints(1,:), ARCorePoints(2,:), ARCorePoints(3,:), 50*ones(numPoints,1), (ARCoreColors ./ 255).','.'); 92 | plot_inertial_frame(0.5); legend(h_ARCore,{'ARCore'}); axis equal; view(26, 73); 93 | xlabel('x [m]','fontsize',10); ylabel('y [m]','fontsize',10); zlabel('z [m]','fontsize',10); hold off; 94 | 95 | % figure options 96 | f = FigureRotator(gca()); 97 | 98 | 99 | % 3) plot roll/pitch/yaw of ARCore device orientation 100 | figure; 101 | subplot(3,1,1); 102 | plot(ARCorePoseTime, stateEsti_ARCore(4,:), 'm'); hold on; grid on; axis tight; 103 | set(gcf,'color','w'); hold off; 104 | axis([min(ARCorePoseTime) max(ARCorePoseTime) min(stateEsti_ARCore(4,:)) max(stateEsti_ARCore(4,:))]); 105 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17); 106 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17); 107 | ylabel('Roll [rad]','FontName','Times New Roman','FontSize',17); 108 | subplot(3,1,2); 109 | plot(ARCorePoseTime, stateEsti_ARCore(5,:), 'm'); hold on; grid on; axis tight; 110 | set(gcf,'color','w'); hold off; 111 | axis([min(ARCorePoseTime) max(ARCorePoseTime) min(stateEsti_ARCore(5,:)) max(stateEsti_ARCore(5,:))]); 112 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17); 113 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17); 114 | ylabel('Pitch [rad]','FontName','Times New Roman','FontSize',17); 115 | subplot(3,1,3); 116 | plot(ARCorePoseTime, stateEsti_ARCore(6,:), 'm'); hold on; grid on; axis tight; 117 | set(gcf,'color','w'); hold off; 118 | axis([min(ARCorePoseTime) max(ARCorePoseTime) min(stateEsti_ARCore(6,:)) max(stateEsti_ARCore(6,:))]); 119 | set(get(gcf,'CurrentAxes'),'FontName','Times New Roman','FontSize',17); 120 | xlabel('Time [sec]','FontName','Times New Roman','FontSize',17); 121 | ylabel('Yaw [rad]','FontName','Times New Roman','FontSize',17); 122 | set(gcf,'Units','pixels','Position',[100 200 1800 900]); % modify figure 123 | 124 | 125 | -------------------------------------------------------------------------------- /Visualization/plot_inertial_frame.m: -------------------------------------------------------------------------------- 1 | function plot_inertial_frame(magnitude) 2 | 3 | 4 | % center point of inertial frame 5 | X = 0; 6 | Y = 0; 7 | Z = 0; 8 | 9 | 10 | % [X Y Z] axis end points of inertial frame 11 | X_axis = [magnitude;0;0]; 12 | Y_axis = [0;magnitude;0]; 13 | Z_axis = [0;0;magnitude]; 14 | 15 | 16 | % draw inertial frame 17 | line([X_axis(1) X],[X_axis(2) Y],[X_axis(3) Z],'Color','r','LineWidth',4) 18 | line([Y_axis(1) X],[Y_axis(2) Y],[Y_axis(3) Z],'Color','g','LineWidth',4) 19 | line([Z_axis(1) X],[Z_axis(2) Y],[Z_axis(3) Z],'Color','b','LineWidth',4) 20 | 21 | 22 | end -------------------------------------------------------------------------------- /Visualization/plot_sensor_ARCore_frame.m: -------------------------------------------------------------------------------- 1 | function [h] = plot_sensor_ARCore_frame(R_gc, p_gc, camScale, camBodyColor) 2 | % Project: Astrobee's mapping and localization system 3 | % Function: plot_sensor_ARCore_frame 4 | % 5 | % Description: 6 | % draw current ARKit camera frame in the current figure 7 | % 8 | % Example: 9 | % OUTPUT: 10 | % h: plot handler 11 | % 12 | % 13 | % INPUT: 14 | % R_gc: rotation matrix of camera frame (T_gc(1:3,1:3)) 15 | % p_gc: position vector of camera frame (T_gc(1:3,4)) 16 | % camScale: scale of camera body and frame 17 | % camBodyColor: color of camera body (NOT FRAME - FRAME is RGB (XYZ)) 18 | % 19 | % 20 | % 21 | % NOTE: 22 | % Copyright 2016 Intelligent Robotics Group, NASA ARC 23 | % 24 | % Author: Pyojin Kim 25 | % Email: pjinkim1215@gmail.com 26 | % Website: 27 | % 28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | % log: 30 | % 2019-06-06: ing 31 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 32 | % 33 | % 34 | 35 | 36 | % option parameters 37 | rows = 960; 38 | cols = 480; 39 | 40 | 41 | % define the camera body and frame 42 | camBody = zeros(3,4); 43 | camBody(:,1) = camScale * [(cols/2);-(rows/2);-(cols-50)] / 1000; 44 | camBody(:,2) = camScale * [-(cols/2);-(rows/2);-(cols-50)] / 1000; 45 | camBody(:,3) = camScale * [-(cols/2);(rows/2);-(cols-50)] / 1000; 46 | camBody(:,4) = camScale * [(cols/2);(rows/2);-(cols-50)] / 1000; 47 | 48 | camFrame = camScale * ((rows/2) / 1000) * eye(3); 49 | 50 | 51 | % rotate the camera body and frame from {C} to {G} 52 | camBody = R_gc * camBody; 53 | camFrame = R_gc * camFrame; 54 | 55 | 56 | % translate the camera body and frame 57 | camBody = camBody + [p_gc(1:3) p_gc(1:3) p_gc(1:3) p_gc(1:3)]; 58 | camFrame = camFrame + [p_gc(1:3) p_gc(1:3) p_gc(1:3)]; 59 | 60 | 61 | % draw the camera body 62 | line([camBody(1,1) p_gc(1)],[camBody(2,1) p_gc(2)],[camBody(3,1) p_gc(3)],'Color', camBodyColor, 'LineWidth', 2) 63 | line([camBody(1,2) p_gc(1)],[camBody(2,2) p_gc(2)],[camBody(3,2) p_gc(3)],'Color', camBodyColor, 'LineWidth', 2) 64 | line([camBody(1,3) p_gc(1)],[camBody(2,3) p_gc(2)],[camBody(3,3) p_gc(3)],'Color', camBodyColor, 'LineWidth', 2) 65 | line([camBody(1,4) p_gc(1)],[camBody(2,4) p_gc(2)],[camBody(3,4) p_gc(3)],'Color', camBodyColor, 'LineWidth', 2) 66 | 67 | line([camBody(1,1) camBody(1,2)],[camBody(2,1) camBody(2,2)],[camBody(3,1) camBody(3,2)],'Color', camBodyColor, 'LineWidth', 2) 68 | line([camBody(1,2) camBody(1,3)],[camBody(2,2) camBody(2,3)],[camBody(3,2) camBody(3,3)],'Color', camBodyColor, 'LineWidth', 2) 69 | line([camBody(1,3) camBody(1,4)],[camBody(2,3) camBody(2,4)],[camBody(3,3) camBody(3,4)],'Color', camBodyColor, 'LineWidth', 2) 70 | h = line([camBody(1,4) camBody(1,1)],[camBody(2,4) camBody(2,1)],[camBody(3,4) camBody(3,1)],'Color', camBodyColor, 'LineWidth', 2); 71 | 72 | 73 | % draw the camera frame 74 | line([camFrame(1,1) p_gc(1)],[camFrame(2,1) p_gc(2)],[camFrame(3,1) p_gc(3)],'Color','r','LineWidth',2) 75 | line([camFrame(1,2) p_gc(1)],[camFrame(2,2) p_gc(2)],[camFrame(3,2) p_gc(3)],'Color','g','LineWidth',2) 76 | line([camFrame(1,3) p_gc(1)],[camFrame(2,3) p_gc(2)],[camFrame(3,3) p_gc(3)],'Color','b','LineWidth',2) 77 | 78 | 79 | end -------------------------------------------------------------------------------- /Visualization/q2r.m: -------------------------------------------------------------------------------- 1 | function R = q2r( q ) 2 | % Project: Patch-based illumination-variant DVO 3 | % Function: q2r 4 | % 5 | % Description: 6 | % This function convert from unit orientation quaternion to rotation matrix 7 | % 8 | % Example: 9 | % OUTPUT: 10 | % R = rotation matrix (3x3) defined as [inertial frame] = R * [body frame] (R = R_gb) 11 | % 12 | % INPUT: 13 | % quatVector: quaternion vector composed of [qw qx qy qz] 14 | % 15 | % NOTE: 16 | % 17 | % Author: Pyojin Kim 18 | % Email: pjinkim1215@gmail.com 19 | % Website: 20 | % 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | % log: 23 | % 2015-02-06: Complete 24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 25 | % 26 | 27 | % quaternion to dcm 28 | q = q/norm(q); 29 | 30 | a = q(1); 31 | b = q(2); 32 | c = q(3); 33 | d = q(4); 34 | R=[ a*a+b*b-c*c-d*d, 2*(b*c-a*d), 2*(b*d+a*c); 35 | 2*(b*c+a*d), a*a-b*b+c*c-d*d, 2*(c*d-a*b); 36 | 2*(b*d-a*c), 2*(c*d+a*b), a*a-b*b-c*c+d*d; ]; 37 | 38 | end -------------------------------------------------------------------------------- /Visualization/r2q.m: -------------------------------------------------------------------------------- 1 | function [q] = r2q( R ) 2 | % Project: Patch-based illumination-variant DVO 3 | % Function: r2q 4 | % 5 | % Description: 6 | % This function convert from rotation matrix to unit orientation quaternion 7 | % 8 | % Example: 9 | % OUTPUT: 10 | % quatVector: quaternion vector composed of [qw qx qy qz] 11 | % 12 | % INPUT: 13 | % R = rotation matrix (3x3) defined as [inertial frame] = R * [body frame] (R = R_gb) 14 | % 15 | % NOTE: 16 | % 17 | % Author: Pyojin Kim 18 | % Email: pjinkim1215@gmail.com 19 | % Website: 20 | % 21 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 22 | % log: 23 | % 2015-02-06: Complete 24 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 25 | % 26 | 27 | q1 = 0.5 * sqrt(1+R(1,1)+R(2,2)+R(3,3)); 28 | q2 = (1/(4*q1))*(R(3,2)-R(2,3)); 29 | q3 = (1/(4*q1))*(R(1,3)-R(3,1)); 30 | q4 = (1/(4*q1))*(R(2,1)-R(1,2)); 31 | 32 | q = [q1;q2;q3;q4]; 33 | 34 | end 35 | 36 | -------------------------------------------------------------------------------- /Visualization/rotmtx2angle.m: -------------------------------------------------------------------------------- 1 | function [eulerAngle]= rotmtx2angle( R ) 2 | % Project: Patch-based Illumination invariant Visual Odometry (PIVO) 3 | % Function: rotmtx2angle 4 | % 5 | % Description: 6 | % This function return the euler angle along x,y and z direction 7 | % from rotation matrix to [phi;theta;psi] angle defined as ZYX sequence 8 | % 9 | % Example: 10 | % OUTPUT: 11 | % eulerAngle: angle vector composed of [phi;theta;psi] 12 | % phi = Rotation angle along x direction in radians 13 | % theta = Rotation angle along y direction in radians 14 | % psi = Rotation angle along z direction in radians 15 | % 16 | % INPUT: 17 | % R = rotation matrix (3x3) defined as [body frame] = R * [inertial frame] (R = R_bg) 18 | % 19 | % NOTE: 20 | % 21 | % Author: Pyojin Kim 22 | % Email: pjinkim1215@gmail.com 23 | % Website: 24 | % 25 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 26 | % log: 27 | % 2017-02-06: Complete 28 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 29 | % 30 | 31 | % change rotMtxBody / [Inertial frame] = rotMtxBody * [Body frame] 32 | rotMtxBody = R.'; 33 | 34 | phi=atan2( rotMtxBody(3,2) , rotMtxBody(3,3) ); 35 | theta=asin( -rotMtxBody(3,1) ); 36 | psi=atan2( rotMtxBody(2,1) , rotMtxBody(1,1) ); 37 | 38 | eulerAngle = [phi;theta;psi]; 39 | 40 | end 41 | 42 | 43 | -------------------------------------------------------------------------------- /Visualization/test_codes.m: -------------------------------------------------------------------------------- 1 | 2 | 3 | % parsing ARCore sensor pose data text file 4 | textFileDir = 'ARCore_point_cloud.txt'; 5 | textARCorePointData = importdata(textFileDir, delimiter, headerlinesIn); 6 | ARCorePoseTime = textARCorePointData.data(:,1).'; 7 | ARCorePointCloud = textARCorePointData.data(:,[2 3 4]).'; 8 | 9 | 10 | figure; 11 | numPoints = size(ARCorePointCloud,2); 12 | scatter3(ARCorePointCloud(1,:), ARCorePointCloud(2,:), ARCorePointCloud(3,:), 40*ones(numPoints,1),'.'); hold on; grid on; axis equal; 13 | plot_inertial_frame(0.5); 14 | xlabel('x [m]','fontsize',10); ylabel('y [m]','fontsize',10); zlabel('z [m]','fontsize',10); hold off; 15 | 16 | 17 | % 2) plot ARKit VIO motion estimation results 18 | figure; 19 | h_ARKit = plot3(stateEsti_ARKit(1,:),stateEsti_ARKit(2,:),stateEsti_ARKit(3,:),'m','LineWidth',2); hold on; grid on; 20 | scatter3(ARKitPoints(1,:), ARKitPoints(2,:), ARKitPoints(3,:), 40*ones(numPoints,1), (ARKitColors ./ 255).','.'); 21 | plot_inertial_frame(0.5); legend(h_ARKit,{'ARKit'}); axis equal; view(26, 73); 22 | xlabel('x [m]','fontsize',10); ylabel('y [m]','fontsize',10); zlabel('z [m]','fontsize',10); hold off; 23 | 24 | % figure options 25 | f = FigureRotator(gca()); 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | scatter3(X(:),Y(:),Z(:),S(:),C(:),'filled'), view(-60,60) 34 | 35 | 36 | 37 | 38 | scatter3(X3DptsGlobal_k(1,:).' , X3DptsGlobal_k(2,:).' , X3DptsGlobal_k(3,:).' , 100*ones(numPts,1) , X3DptsColor_k.','.'); -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | apply plugin: 'com.android.application' 16 | 17 | android { 18 | compileSdkVersion 29 19 | buildToolsVersion "29.0.1" 20 | defaultConfig { 21 | applicationId "com.pjinkim.arcore_data_logger" 22 | minSdkVersion 26 23 | targetSdkVersion 29 24 | versionCode 1 25 | versionName "1.0" 26 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 27 | } 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | buildTypes { 33 | release { 34 | minifyEnabled false 35 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 36 | } 37 | } 38 | } 39 | 40 | dependencies { 41 | implementation "com.google.ar.sceneform.ux:sceneform-ux:1.11.0" 42 | implementation fileTree(dir: 'libs', include: ['*.jar']) 43 | implementation 'androidx.appcompat:appcompat:1.0.2' 44 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 45 | testImplementation 'junit:junit:4.12' 46 | androidTestImplementation 'androidx.test:runner:1.2.0' 47 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 48 | } 49 | 50 | apply plugin: 'com.google.ar.sceneform.plugin' 51 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/pjinkim/arcore_data_logger/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.pjinkim.arcore_data_logger; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.InstrumentationRegistry; 6 | import androidx.test.runner.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getTargetContext(); 24 | 25 | assertEquals("com.pjinkim.arcore_data_logger", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 35 | 36 | 37 | 40 | 41 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyojinKim/ARCore-Data-Logger/7ca9ea331313b36cb00ef2e3a28908540ea2b126/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/pjinkim/arcore_data_logger/ARCoreSession.java: -------------------------------------------------------------------------------- 1 | package com.pjinkim.arcore_data_logger; 2 | 3 | import android.content.Context; 4 | import android.graphics.Bitmap; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Color; 7 | import android.graphics.ImageFormat; 8 | import android.graphics.Matrix; 9 | import android.graphics.Rect; 10 | import android.graphics.YuvImage; 11 | import android.media.Image; 12 | import android.util.Log; 13 | 14 | import androidx.annotation.NonNull; 15 | 16 | import com.google.ar.core.Camera; 17 | import com.google.ar.core.Frame; 18 | import com.google.ar.core.PointCloud; 19 | import com.google.ar.core.Pose; 20 | import com.google.ar.core.TrackingFailureReason; 21 | import com.google.ar.core.TrackingState; 22 | import com.google.ar.core.exceptions.NotYetAvailableException; 23 | import com.google.ar.sceneform.FrameTime; 24 | import com.google.ar.sceneform.math.Vector3; 25 | import com.google.ar.sceneform.ux.ArFragment; 26 | 27 | import java.io.BufferedWriter; 28 | import java.io.ByteArrayOutputStream; 29 | import java.io.IOException; 30 | import java.nio.ByteBuffer; 31 | import java.nio.FloatBuffer; 32 | import java.nio.IntBuffer; 33 | import java.security.KeyException; 34 | import java.util.ArrayList; 35 | import java.util.Locale; 36 | import java.util.concurrent.atomic.AtomicBoolean; 37 | 38 | public class ARCoreSession { 39 | 40 | // properties 41 | private static final String LOG_TAG = ARCoreSession.class.getName(); 42 | private static final long mulSecondToNanoSecond = 1000000000; 43 | private long previousTimestamp = 0; 44 | 45 | private MainActivity mContext; 46 | private ArFragment mArFragment; 47 | private PointCloudNode mPointCloudNode; 48 | private AccumulatedPointCloud mAccumulatedPointCloud; 49 | private WorldToScreenTranslator mWorldToScreenTranslator; 50 | private ARCoreResultStreamer mFileStreamer = null; 51 | 52 | private AtomicBoolean mIsRecording = new AtomicBoolean(false); 53 | private AtomicBoolean mIsWritingFile = new AtomicBoolean(false); 54 | 55 | private int mNumberOfFeatures = 0; 56 | private TrackingState mTrackingState; 57 | private TrackingFailureReason mTrackingFailureReason; 58 | private double mUpdateRate = 0; 59 | 60 | 61 | // constructor 62 | public ARCoreSession(@NonNull MainActivity context) { 63 | 64 | // initialize object and ARCore fragment 65 | mContext = context; 66 | mArFragment = (ArFragment) mContext.getSupportFragmentManager().findFragmentById(R.id.ux_fragment); 67 | mArFragment.getArSceneView().getPlaneRenderer().setVisible(false); 68 | mArFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdateFrame); 69 | 70 | // render 3D point cloud on the screen 71 | mPointCloudNode = new PointCloudNode(mContext); 72 | mArFragment.getArSceneView().getScene().addChild(mPointCloudNode); 73 | mAccumulatedPointCloud = new AccumulatedPointCloud(); 74 | mWorldToScreenTranslator = new WorldToScreenTranslator(); 75 | } 76 | 77 | 78 | // methods 79 | public void startSession(String streamFolder) { 80 | 81 | // initialize text file stream 82 | if (streamFolder != null) { 83 | try { 84 | mFileStreamer = new ARCoreResultStreamer(mContext, streamFolder); 85 | mIsWritingFile.set(true); 86 | } catch (IOException e) { 87 | mContext.showToast("Cannot create file for ARCore tracking results."); 88 | e.printStackTrace(); 89 | } 90 | } 91 | mIsRecording.set(true); 92 | } 93 | 94 | 95 | public void stopSession() { 96 | 97 | // save ARCore 3D point cloud only for visualization 98 | ArrayList pointsPosition = mAccumulatedPointCloud.getPoints(); 99 | ArrayList pointsColor = mAccumulatedPointCloud.getColors(); 100 | for (int i = 0; i < pointsPosition.size(); i++) { 101 | Vector3 currentPointPosition = pointsPosition.get(i); 102 | Vector3 currentPointColor = pointsColor.get(i); 103 | float pointX = currentPointPosition.x; 104 | float pointY = currentPointPosition.y; 105 | float pointZ = currentPointPosition.z; 106 | float r = currentPointColor.x; 107 | float g = currentPointColor.y; 108 | float b = currentPointColor.z; 109 | try { 110 | mFileStreamer.addARCorePointRecord(pointX, pointY, pointZ, r, g, b); 111 | } catch (IOException | KeyException e) { 112 | Log.d(LOG_TAG, "stopSession: Something is wrong."); 113 | e.printStackTrace(); 114 | } 115 | } 116 | 117 | // close text file and reset variables 118 | if (mIsWritingFile.get()) { 119 | try { 120 | mFileStreamer.endFiles(); 121 | } catch (IOException e) { 122 | e.printStackTrace(); 123 | } 124 | } 125 | mIsWritingFile.set(false); 126 | mIsRecording.set(false); 127 | } 128 | 129 | 130 | private void onUpdateFrame(FrameTime frameTime) { 131 | 132 | // set some variables 133 | boolean isFileSaved = (mIsRecording.get() && mIsWritingFile.get()); 134 | 135 | // obtain current ARCore information 136 | mArFragment.onUpdate(frameTime); 137 | Frame frame = mArFragment.getArSceneView().getArFrame(); 138 | Camera camera = frame.getCamera(); 139 | 140 | // update ARCore measurements 141 | long timestamp = frame.getTimestamp(); 142 | double updateRate = (double) mulSecondToNanoSecond / (double) (timestamp - previousTimestamp); 143 | previousTimestamp = timestamp; 144 | 145 | TrackingState trackingState = camera.getTrackingState(); 146 | TrackingFailureReason trackingFailureReason = camera.getTrackingFailureReason(); 147 | Pose T_gc = frame.getAndroidSensorPose(); 148 | 149 | float qx = T_gc.qx(); 150 | float qy = T_gc.qy(); 151 | float qz = T_gc.qz(); 152 | float qw = T_gc.qw(); 153 | 154 | float tx = T_gc.tx(); 155 | float ty = T_gc.ty(); 156 | float tz = T_gc.tz(); 157 | 158 | // update 3D point cloud from ARCore 159 | PointCloud pointCloud = frame.acquirePointCloud(); 160 | IntBuffer bufferPointID = pointCloud.getIds(); 161 | FloatBuffer bufferPoint3D = pointCloud.getPoints(); 162 | mPointCloudNode.visualize(pointCloud); 163 | int numberOfFeatures = mAccumulatedPointCloud.getNumberOfFeatures(); 164 | pointCloud.release(); 165 | 166 | // display and save ARCore information 167 | try { 168 | mNumberOfFeatures = numberOfFeatures; 169 | mTrackingState = trackingState; 170 | mTrackingFailureReason = trackingFailureReason; 171 | mUpdateRate = updateRate; 172 | if (isFileSaved) { 173 | 174 | // 1) record ARCore 6-DoF sensor pose 175 | mFileStreamer.addARCorePoseRecord(timestamp, qx, qy, qz, qw, tx, ty, tz); 176 | 177 | // 2) record ARCore 3D point cloud only for visualization 178 | Image imageFrame = frame.acquireCameraImage(); 179 | Bitmap imageBitmap = imageToBitmap(imageFrame); 180 | imageFrame.close(); 181 | for (int i = 0; i < (bufferPoint3D.limit() / 4); i++) { 182 | 183 | // check each point's confidence level 184 | float pointConfidence = bufferPoint3D.get(i * 4 + 3); 185 | if (pointConfidence < 0.5) { 186 | continue; 187 | } 188 | 189 | // obtain point ID and XYZ world position 190 | int pointID = bufferPointID.get(i); 191 | float pointX = bufferPoint3D.get(i * 4); 192 | float pointY = bufferPoint3D.get(i * 4 + 1); 193 | float pointZ = bufferPoint3D.get(i * 4 + 2); 194 | 195 | // get each point RGB color information 196 | float[] worldPosition = new float[]{pointX, pointY, pointZ}; 197 | Vector3 pointColor = getScreenPixel(worldPosition, imageBitmap); 198 | if (pointColor == null) { 199 | continue; 200 | } 201 | 202 | // append each point position and color information 203 | mAccumulatedPointCloud.appendPointCloud(pointID, pointX, pointY, pointZ, pointColor.x, pointColor.y, pointColor.z); 204 | } 205 | } 206 | } catch (IOException | KeyException | NotYetAvailableException e) { 207 | Log.d(LOG_TAG, "onUpdateFrame: Something is wrong."); 208 | e.printStackTrace(); 209 | } 210 | } 211 | 212 | 213 | private Bitmap imageToBitmap (Image image) { 214 | int width = image.getWidth(); 215 | int height = image.getHeight(); 216 | 217 | byte[] nv21; 218 | ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); 219 | ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); 220 | ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); 221 | 222 | int ySize = yBuffer.remaining(); 223 | int uSize = uBuffer.remaining(); 224 | int vSize = vBuffer.remaining(); 225 | 226 | nv21 = new byte[ySize + uSize + vSize]; 227 | 228 | // U and V are swapped 229 | yBuffer.get(nv21, 0, ySize); 230 | vBuffer.get(nv21, ySize, vSize); 231 | uBuffer.get(nv21, ySize + vSize, uSize); 232 | 233 | YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, width, height, null); 234 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 235 | yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, os); 236 | byte[] jpegByteArray = os.toByteArray(); 237 | Bitmap bitmap = BitmapFactory.decodeByteArray(jpegByteArray, 0, jpegByteArray.length); 238 | 239 | Matrix matrix = new Matrix(); 240 | matrix.setRotate(90); 241 | 242 | return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); 243 | } 244 | 245 | 246 | private Vector3 getScreenPixel(float[] worldPosition, Bitmap imageBitmap) throws NotYetAvailableException { 247 | 248 | // clip to screen space (ViewMatrix * ProjectionMatrix * Anchor Matrix) 249 | double[] pos2D = mWorldToScreenTranslator.worldToScreen(imageBitmap.getWidth(), imageBitmap.getHeight(), mArFragment.getArSceneView().getArFrame().getCamera(), worldPosition); 250 | 251 | // check if inside the screen 252 | if ((pos2D[0] < 0) || (pos2D[0] > imageBitmap.getWidth()) || (pos2D[1] < 0) || (pos2D[1] > imageBitmap.getHeight())) { 253 | return null; 254 | } 255 | 256 | int pixel = imageBitmap.getPixel((int) pos2D[0], (int) pos2D[1]); 257 | int r = Color.red(pixel); 258 | int g = Color.green(pixel); 259 | int b = Color.blue(pixel); 260 | Vector3 pointColor = new Vector3(r, g, b); 261 | 262 | return pointColor; 263 | } 264 | 265 | 266 | // definition of 'ARCoreResultStreamer' class 267 | class ARCoreResultStreamer extends FileStreamer { 268 | 269 | // properties 270 | private BufferedWriter mWriterPose; 271 | private BufferedWriter mWriterPoint; 272 | 273 | 274 | // constructor 275 | ARCoreResultStreamer(final Context context, final String outputFolder) throws IOException { 276 | super(context, outputFolder); 277 | addFile("ARCore_sensor_pose", "ARCore_sensor_pose.txt"); 278 | addFile("ARCore_point_cloud", "ARCore_point_cloud.txt"); 279 | mWriterPose = getFileWriter("ARCore_sensor_pose"); 280 | mWriterPoint = getFileWriter("ARCore_point_cloud"); 281 | } 282 | 283 | 284 | // methods 285 | public void addARCorePoseRecord(long timestamp, float qx, float qy, float qz, float qw, float tx, float ty, float tz) throws IOException, KeyException { 286 | 287 | // execute the block with only one thread 288 | synchronized (this) { 289 | 290 | // record timestamp and 6-DoF device pose in text file 291 | StringBuilder stringBuilder = new StringBuilder(); 292 | stringBuilder.append(timestamp); 293 | stringBuilder.append(String.format(Locale.US, " %.6f %.6f %.6f %.6f %.6f %.6f %.6f", qx, qy, qz, qw, tx, ty, tz)); 294 | stringBuilder.append(" \n"); 295 | mWriterPose.write(stringBuilder.toString()); 296 | } 297 | } 298 | 299 | 300 | public void addARCorePointRecord(final float pointX, final float pointY, final float pointZ, final float r, final float g, final float b) throws IOException, KeyException { 301 | 302 | // execute the block with only one thread 303 | synchronized (this) { 304 | 305 | // record 3D point cloud in text file 306 | StringBuilder stringBuilder = new StringBuilder(); 307 | stringBuilder.append(String.format(Locale.US, "%.6f %.6f %.6f %.2f %.2f %.2f", pointX, pointY, pointZ, r, g, b)); 308 | stringBuilder.append(" \n"); 309 | mWriterPoint.write(stringBuilder.toString()); 310 | } 311 | } 312 | 313 | 314 | @Override 315 | public void endFiles() throws IOException { 316 | 317 | // execute the block with only one thread 318 | synchronized (this) { 319 | mWriterPose.flush(); 320 | mWriterPose.close(); 321 | mWriterPoint.flush(); 322 | mWriterPoint.close(); 323 | } 324 | } 325 | } 326 | 327 | 328 | // getter and setter 329 | public int getNumberOfFeatures() { 330 | return mNumberOfFeatures; 331 | } 332 | 333 | public TrackingState getTrackingState() { 334 | return mTrackingState; 335 | } 336 | 337 | public TrackingFailureReason getTrackingFailureReason() { 338 | return mTrackingFailureReason; 339 | } 340 | 341 | public double getUpdateRate() { 342 | return mUpdateRate; 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /app/src/main/java/com/pjinkim/arcore_data_logger/AccumulatedPointCloud.java: -------------------------------------------------------------------------------- 1 | package com.pjinkim.arcore_data_logger; 2 | 3 | import com.google.ar.sceneform.math.Vector3; 4 | 5 | import java.util.ArrayList; 6 | 7 | public class AccumulatedPointCloud { 8 | 9 | // properties 10 | private static final int BASE_CAPACITY = 100000; 11 | private ArrayList mPoints = new ArrayList(); 12 | private ArrayList mColors = new ArrayList(); 13 | private int[] mIdentifiedIndices = new int[BASE_CAPACITY]; 14 | private int mNumberOfFeatures = 0; 15 | 16 | 17 | // constructor 18 | public AccumulatedPointCloud() { 19 | 20 | // initialize properties 21 | for (int i = 0; i < BASE_CAPACITY; i++) { 22 | mIdentifiedIndices[i] = -99; 23 | } 24 | } 25 | 26 | 27 | // methods 28 | public void appendPointCloud(int pointID, float pointX, float pointY, float pointZ, float r, float g, float b) { 29 | if (mIdentifiedIndices[pointID] != -99) { 30 | int existingIndex = mIdentifiedIndices[pointID]; 31 | Vector3 pointPosition = new Vector3(pointX, pointY, pointZ); 32 | Vector3 pointColor = new Vector3(r, g, b); 33 | mPoints.set(existingIndex, pointPosition); 34 | mColors.set(existingIndex, pointColor); 35 | } else { 36 | mIdentifiedIndices[pointID] = mNumberOfFeatures; 37 | Vector3 pointPosition = new Vector3(pointX, pointY, pointZ); 38 | Vector3 pointColor = new Vector3(r, g, b); 39 | mPoints.add(pointPosition); 40 | mColors.add(pointColor); 41 | mNumberOfFeatures++; 42 | } 43 | } 44 | 45 | 46 | // getter and setter 47 | public int getNumberOfFeatures() { 48 | return mNumberOfFeatures; 49 | } 50 | 51 | public ArrayList getPoints() { 52 | return mPoints; 53 | } 54 | 55 | public ArrayList getColors() { 56 | return mColors; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/pjinkim/arcore_data_logger/FileStreamer.java: -------------------------------------------------------------------------------- 1 | package com.pjinkim.arcore_data_logger; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.util.Log; 7 | 8 | import java.io.BufferedWriter; 9 | import java.io.File; 10 | import java.io.FileWriter; 11 | import java.io.IOException; 12 | import java.security.KeyException; 13 | import java.util.Calendar; 14 | import java.util.HashMap; 15 | import java.util.Locale; 16 | 17 | public class FileStreamer { 18 | 19 | // properties 20 | private final static String LOG_TAG = FileStreamer.class.getName(); 21 | 22 | private Context mContext; 23 | private HashMap mFileWriters = new HashMap<>(); 24 | private String mOutputFolder; 25 | 26 | 27 | // constructor 28 | public FileStreamer(Context mContext, final String mOutputFolder) { 29 | this.mContext = mContext; 30 | this.mOutputFolder = mOutputFolder; 31 | } 32 | 33 | 34 | // methods 35 | public void addFile(final String writerId, final String fileName) throws IOException { 36 | 37 | // check if there is a already generated text file 38 | if (mFileWriters.containsKey(writerId)) { 39 | Log.w(LOG_TAG, "addFile: " + writerId + " already exist."); 40 | return; 41 | } 42 | 43 | // get current time information 44 | Calendar fileTimestamp = Calendar.getInstance(); 45 | String timeHeader = "# Created at " + fileTimestamp.getTime().toString() + " in Burnaby Canada \n"; 46 | 47 | // generate text file 48 | BufferedWriter newWriter = createFile(mOutputFolder + "/" + fileName, timeHeader); 49 | mFileWriters.put(writerId, newWriter); 50 | } 51 | 52 | private BufferedWriter createFile(final String path, final String timeHeader) throws IOException { 53 | 54 | File file = new File(path); 55 | BufferedWriter writer = new BufferedWriter((new FileWriter(file))); 56 | 57 | Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); 58 | scanIntent.setData(Uri.fromFile(file)); 59 | mContext.sendBroadcast(scanIntent); 60 | if ((timeHeader != null) && (timeHeader.length() != 0)) { 61 | writer.append(timeHeader); 62 | writer.flush(); 63 | } 64 | return writer; 65 | } 66 | 67 | public String getOutputFolder() { 68 | return mOutputFolder; 69 | } 70 | 71 | public BufferedWriter getFileWriter(final String writerId) { 72 | return mFileWriters.get(writerId); 73 | } 74 | 75 | public void addRecord(final long timestamp, final String writerId, final int numValues, final float[] values) throws IOException, KeyException { 76 | 77 | // execute the block with only one thread 78 | synchronized (this) { 79 | 80 | // get BufferedWriter of 'writerId' 81 | BufferedWriter writer = getFileWriter(writerId); 82 | if (writer == null) { 83 | throw new KeyException("addRecord: " + writerId + " not found."); 84 | } 85 | 86 | // record timestamp, and values in text file 87 | StringBuilder stringBuilder = new StringBuilder(); 88 | stringBuilder.append(timestamp); 89 | for (int i = 0; i < numValues; ++i) { 90 | stringBuilder.append(String.format(Locale.US, " %.6f", values[i])); 91 | } 92 | stringBuilder.append(" \n"); 93 | writer.write(stringBuilder.toString()); 94 | } 95 | } 96 | 97 | public void endFiles() throws IOException { 98 | 99 | // execute the block with only one thread 100 | synchronized (this) { 101 | for (BufferedWriter eachWriter : mFileWriters.values()) { 102 | eachWriter.flush(); 103 | eachWriter.close(); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /app/src/main/java/com/pjinkim/arcore_data_logger/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.pjinkim.arcore_data_logger; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.core.content.ContextCompat; 5 | 6 | import android.Manifest; 7 | import android.app.Activity; 8 | import android.app.ActivityManager; 9 | import android.content.Context; 10 | import android.content.pm.PackageManager; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | import android.os.Handler; 14 | import android.os.PowerManager; 15 | import android.util.Log; 16 | import android.view.View; 17 | import android.widget.Button; 18 | import android.widget.TextView; 19 | import android.widget.Toast; 20 | 21 | import com.google.ar.core.TrackingFailureReason; 22 | import com.google.ar.core.TrackingState; 23 | 24 | import java.io.IOException; 25 | import java.util.Locale; 26 | import java.util.Timer; 27 | import java.util.TimerTask; 28 | import java.util.concurrent.atomic.AtomicBoolean; 29 | 30 | public class MainActivity extends AppCompatActivity { 31 | 32 | // properties 33 | private static final String LOG_TAG = MainActivity.class.getName(); 34 | private static final double MIN_OPENGL_VERSION = 3.0; 35 | 36 | private final static int REQUEST_CODE_ANDROID = 1001; 37 | private static String[] REQUIRED_PERMISSIONS = new String[] { 38 | Manifest.permission.CAMERA, 39 | Manifest.permission.WAKE_LOCK, 40 | Manifest.permission.READ_PHONE_STATE, 41 | Manifest.permission.WRITE_EXTERNAL_STORAGE 42 | }; 43 | 44 | private ARCoreSession mARCoreSession; 45 | 46 | private Handler mHandler = new Handler(); 47 | private AtomicBoolean mIsRecording = new AtomicBoolean(false); 48 | private PowerManager.WakeLock mWakeLock; 49 | 50 | private TextView mLabelNumberFeatures, mLabelUpdateRate; 51 | private TextView mLabelTrackingStatus, mLabelTrackingFailureReason; 52 | 53 | private Button mStartStopButton; 54 | private TextView mLabelInterfaceTime; 55 | private Timer mInterfaceTimer = new Timer(); 56 | private int mSecondCounter = 0; 57 | 58 | 59 | // Android activity lifecycle states 60 | @Override 61 | protected void onCreate(Bundle savedInstanceState) { 62 | super.onCreate(savedInstanceState); 63 | setContentView(R.layout.activity_main); 64 | 65 | // check Android and OpenGL version 66 | if (!checkIsSupportedDeviceOrFinish(this)) { 67 | return; 68 | } 69 | 70 | 71 | // initialize screen labels and buttons 72 | initializeViews(); 73 | 74 | 75 | // setup sessions 76 | mARCoreSession = new ARCoreSession(this); 77 | 78 | 79 | // battery power setting 80 | PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); 81 | mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "sensors_data_logger:wakelocktag"); 82 | mWakeLock.acquire(); 83 | 84 | 85 | // monitor ARCore information 86 | displayARCoreInformation(); 87 | mLabelInterfaceTime.setText(R.string.ready_title); 88 | } 89 | 90 | 91 | @Override 92 | protected void onResume() { 93 | super.onResume(); 94 | if (!hasPermissions(this, REQUIRED_PERMISSIONS)) { 95 | requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_ANDROID); 96 | } 97 | } 98 | 99 | 100 | @Override 101 | protected void onDestroy() { 102 | if (mIsRecording.get()) { 103 | stopRecording(); 104 | } 105 | if (mWakeLock.isHeld()) { 106 | mWakeLock.release(); 107 | } 108 | super.onDestroy(); 109 | } 110 | 111 | 112 | // methods 113 | public void startStopRecording(View view) { 114 | if (!mIsRecording.get()) { 115 | 116 | // start recording sensor measurements when button is pressed 117 | startRecording(); 118 | 119 | // start interface timer on display 120 | mSecondCounter = 0; 121 | mInterfaceTimer.schedule(new TimerTask() { 122 | @Override 123 | public void run() { 124 | mSecondCounter += 1; 125 | mLabelInterfaceTime.setText(interfaceIntTime(mSecondCounter)); 126 | } 127 | }, 0, 1000); 128 | 129 | } else { 130 | 131 | // stop recording sensor measurements when button is pressed 132 | stopRecording(); 133 | 134 | // stop interface timer on display 135 | mInterfaceTimer.cancel(); 136 | mLabelInterfaceTime.setText(R.string.ready_title); 137 | } 138 | } 139 | 140 | 141 | private void startRecording() { 142 | 143 | // output directory for text files 144 | String outputFolder = null; 145 | try { 146 | OutputDirectoryManager folder = new OutputDirectoryManager("", "R_pjinkim_ARCore"); 147 | outputFolder = folder.getOutputDirectory(); 148 | } catch (IOException e) { 149 | Log.e(LOG_TAG, "startRecording: Cannot create output folder."); 150 | e.printStackTrace(); 151 | } 152 | 153 | // start ARCore session 154 | mARCoreSession.startSession(outputFolder); 155 | mIsRecording.set(true); 156 | 157 | // update Start/Stop button UI 158 | runOnUiThread(new Runnable() { 159 | @Override 160 | public void run() { 161 | mStartStopButton.setEnabled(true); 162 | mStartStopButton.setText(R.string.stop_title); 163 | } 164 | }); 165 | showToast("Recording starts!"); 166 | } 167 | 168 | 169 | protected void stopRecording() { 170 | mHandler.post(new Runnable() { 171 | @Override 172 | public void run() { 173 | 174 | // stop ARCore session 175 | mARCoreSession.stopSession(); 176 | mIsRecording.set(false); 177 | 178 | // update screen UI and button 179 | showToast("Recording stops!"); 180 | resetUI(); 181 | } 182 | }); 183 | } 184 | 185 | 186 | public void showToast(final String text) { 187 | runOnUiThread(new Runnable() { 188 | @Override 189 | public void run() { 190 | Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show(); 191 | } 192 | }); 193 | } 194 | 195 | 196 | private void resetUI() { 197 | runOnUiThread(new Runnable() { 198 | @Override 199 | public void run() { 200 | mLabelNumberFeatures.setText("N/A"); 201 | mLabelTrackingStatus.setText("N/A"); 202 | mLabelTrackingFailureReason.setText("N/A"); 203 | mLabelUpdateRate.setText("N/A"); 204 | 205 | mStartStopButton.setEnabled(true); 206 | mStartStopButton.setText(R.string.start_title); 207 | } 208 | }); 209 | } 210 | 211 | 212 | public static boolean checkIsSupportedDeviceOrFinish(final Activity activity) { 213 | 214 | // check Android version 215 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { 216 | Log.e(LOG_TAG, "Sceneform requires Android N or later"); 217 | Toast.makeText(activity, "Sceneform requires Android N or later", Toast.LENGTH_LONG).show(); 218 | activity.finish(); 219 | return false; 220 | } 221 | 222 | // get current OpenGL version 223 | String openGlVersionString = ((ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE)) 224 | .getDeviceConfigurationInfo() 225 | .getGlEsVersion(); 226 | 227 | // check OpenGL version 228 | if (Double.parseDouble(openGlVersionString) < MIN_OPENGL_VERSION) { 229 | Log.e(LOG_TAG, "Sceneform requires OpenGL ES 3.0 later"); 230 | Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG).show(); 231 | activity.finish(); 232 | return false; 233 | } 234 | return true; 235 | } 236 | 237 | 238 | @Override 239 | public void onBackPressed() { 240 | 241 | // nullify back button when recording starts 242 | if (!mIsRecording.get()) { 243 | super.onBackPressed(); 244 | } 245 | } 246 | 247 | 248 | private static boolean hasPermissions(Context context, String... permissions) { 249 | 250 | // check Android hardware permissions 251 | for (String permission : permissions) { 252 | if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { 253 | return false; 254 | } 255 | } 256 | return true; 257 | } 258 | 259 | 260 | private String interfaceIntTime(final int second) { 261 | 262 | // check second input 263 | if (second < 0) { 264 | Log.e(LOG_TAG, "interfaceIntTime: Second cannot be negative."); 265 | return null; 266 | } 267 | 268 | // extract hour, minute, second information from second 269 | int input = second; 270 | int hours = input / 3600; 271 | input = input % 3600; 272 | int mins = input / 60; 273 | int secs = input % 60; 274 | 275 | // return interface int time 276 | return String.format(Locale.US, "%02d:%02d:%02d", hours, mins, secs); 277 | } 278 | 279 | 280 | private void initializeViews() { 281 | 282 | mLabelNumberFeatures = (TextView) findViewById(R.id.label_number_features); 283 | mLabelTrackingStatus = (TextView) findViewById(R.id.label_tracking_status); 284 | mLabelTrackingFailureReason = (TextView) findViewById(R.id.label_tracking_failure_reason); 285 | mLabelUpdateRate = (TextView) findViewById(R.id.label_update_rate); 286 | 287 | mStartStopButton = (Button) findViewById(R.id.button_start_stop); 288 | mLabelInterfaceTime = (TextView) findViewById(R.id.label_interface_time); 289 | } 290 | 291 | 292 | private void displayARCoreInformation() { 293 | 294 | // get ARCore tracking information 295 | int numberOfFeatures = mARCoreSession.getNumberOfFeatures(); 296 | TrackingState trackingState = mARCoreSession.getTrackingState(); 297 | TrackingFailureReason trackingFailureReason = mARCoreSession.getTrackingFailureReason(); 298 | double updateRate = mARCoreSession.getUpdateRate(); 299 | 300 | // update current screen (activity) 301 | runOnUiThread(new Runnable() { 302 | @Override 303 | public void run() { 304 | 305 | // determine TrackingState text 306 | String ARCoreTrackingState = ""; 307 | if (trackingState == TrackingState.PAUSED) { 308 | ARCoreTrackingState = "PAUSED"; 309 | } else if (trackingState == TrackingState.STOPPED) { 310 | ARCoreTrackingState = "STOPPED"; 311 | } else if (trackingState == TrackingState.TRACKING) { 312 | ARCoreTrackingState = "TRACKING"; 313 | } else { 314 | ARCoreTrackingState = "ERROR?"; 315 | } 316 | 317 | // determine TrackingFailureReason text 318 | String ARCoreTrackingFailureReason = ""; 319 | if (trackingFailureReason == TrackingFailureReason.BAD_STATE) { 320 | ARCoreTrackingFailureReason = "BAD STATE"; 321 | } else if (trackingFailureReason == TrackingFailureReason.EXCESSIVE_MOTION) { 322 | ARCoreTrackingFailureReason = "FAST MOTION"; 323 | } else if (trackingFailureReason == TrackingFailureReason.INSUFFICIENT_FEATURES) { 324 | ARCoreTrackingFailureReason = "LOW FEATURES"; 325 | } else if (trackingFailureReason == TrackingFailureReason.INSUFFICIENT_LIGHT) { 326 | ARCoreTrackingFailureReason = "LOW LIGHT"; 327 | } else if (trackingFailureReason == TrackingFailureReason.NONE) { 328 | ARCoreTrackingFailureReason = "NONE"; 329 | } else { 330 | ARCoreTrackingFailureReason = "ERROR?"; 331 | } 332 | 333 | // update interface screen labels 334 | mLabelNumberFeatures.setText(String.format(Locale.US, "%05d", numberOfFeatures)); 335 | mLabelTrackingStatus.setText(ARCoreTrackingState); 336 | mLabelTrackingFailureReason.setText(ARCoreTrackingFailureReason); 337 | mLabelUpdateRate.setText(String.format(Locale.US, "%.3f Hz", updateRate)); 338 | } 339 | }); 340 | 341 | // determine display update rate (100 ms) 342 | final long displayInterval = 100; 343 | mHandler.postDelayed(new Runnable() { 344 | @Override 345 | public void run() { 346 | displayARCoreInformation(); 347 | } 348 | }, displayInterval); 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /app/src/main/java/com/pjinkim/arcore_data_logger/OutputDirectoryManager.java: -------------------------------------------------------------------------------- 1 | package com.pjinkim.arcore_data_logger; 2 | 3 | import android.os.Environment; 4 | import android.util.Log; 5 | 6 | import java.io.File; 7 | import java.io.FileNotFoundException; 8 | import java.text.SimpleDateFormat; 9 | import java.util.Calendar; 10 | 11 | public class OutputDirectoryManager { 12 | 13 | // properties 14 | private final static String LOG_TAG = OutputDirectoryManager.class.getName(); 15 | 16 | private String mOutputDirectory; 17 | 18 | 19 | // constructors 20 | public OutputDirectoryManager(final String prefix, final String suffix) throws FileNotFoundException { 21 | update(prefix, suffix); 22 | } 23 | 24 | public OutputDirectoryManager(final String prefix) throws FileNotFoundException { 25 | update(prefix); 26 | } 27 | 28 | public OutputDirectoryManager() throws FileNotFoundException { 29 | update(); 30 | } 31 | 32 | 33 | // methods 34 | private void update(final String prefix, final String suffix) throws FileNotFoundException { 35 | 36 | // initialize folder name with current time information 37 | Calendar currentTime = Calendar.getInstance(); 38 | SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddhhmmss"); 39 | File externalDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); 40 | String folderName = formatter.format(currentTime.getTime()); 41 | 42 | // combine prefix and suffix 43 | if (prefix != null) { 44 | folderName = prefix + folderName; 45 | } 46 | if (suffix != null) { 47 | folderName = folderName + suffix; 48 | } 49 | 50 | // generate output directory folder 51 | File outputDirectory = new File(externalDirectory.getAbsolutePath() + "/" + folderName); 52 | if (!outputDirectory.exists()) { 53 | if (!outputDirectory.mkdir()) { 54 | Log.e(LOG_TAG, "update: Cannot create output directory."); 55 | throw new FileNotFoundException(); 56 | } 57 | } 58 | mOutputDirectory = outputDirectory.getAbsolutePath(); 59 | Log.i(LOG_TAG, "update: Output directory: " + outputDirectory.getAbsolutePath()); 60 | } 61 | 62 | private void update(final String prefix) throws FileNotFoundException { 63 | update(prefix, null); 64 | } 65 | 66 | private void update() throws FileNotFoundException { 67 | update(null, null); 68 | } 69 | 70 | 71 | // getter and setter 72 | public String getOutputDirectory() { 73 | return mOutputDirectory; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/pjinkim/arcore_data_logger/PointCloudNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.pjinkim.arcore_data_logger; 17 | 18 | import android.content.Context; 19 | import android.util.Log; 20 | 21 | import com.google.ar.core.PointCloud; 22 | import com.google.ar.sceneform.Node; 23 | import com.google.ar.sceneform.math.Vector3; 24 | import com.google.ar.sceneform.rendering.Color; 25 | import com.google.ar.sceneform.rendering.Material; 26 | import com.google.ar.sceneform.rendering.MaterialFactory; 27 | import com.google.ar.sceneform.rendering.ModelRenderable; 28 | import com.google.ar.sceneform.rendering.RenderableDefinition; 29 | import com.google.ar.sceneform.rendering.Vertex; 30 | 31 | import java.nio.FloatBuffer; 32 | import java.util.concurrent.CompletableFuture; 33 | import java.util.stream.Collectors; 34 | import java.util.stream.IntStream; 35 | import java.util.stream.Stream; 36 | 37 | /** Renders the ARCore point cloud as a Node. */ 38 | public class PointCloudNode extends Node { 39 | 40 | // properties 41 | private static final String LOG_TAG = PointCloudNode.class.getName(); 42 | 43 | private long timestamp; 44 | private Vertex[] ptbuffer; 45 | private int[] indexbuffer; 46 | private int numFeatures; 47 | 48 | private CompletableFuture materialHolder; 49 | 50 | // This is the extent of the point 51 | private static final float POINT_DELTA = 0.003f; 52 | 53 | 54 | // constructor 55 | public PointCloudNode(Context context) { 56 | float r = 255 / 255f; 57 | float g = 0 / 255f; 58 | float b = 255 / 255f; 59 | 60 | Color color = new Color(r, g, b, 1.0f); 61 | materialHolder = MaterialFactory.makeOpaqueWithColor(context, color); 62 | } 63 | 64 | 65 | // methods 66 | @SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"}) 67 | public void visualize(PointCloud cloud) { 68 | 69 | if (!isEnabled()) { 70 | return; 71 | } 72 | // If this is the same cloud as last time, skip it. Also, if the material has not loaded yet, 73 | // skip this. 74 | if (this.timestamp != cloud.getTimestamp() && materialHolder.getNow(null) != null) { 75 | timestamp = cloud.getTimestamp(); 76 | FloatBuffer buf = cloud.getPoints(); 77 | 78 | // Point clouds are 4 values x,y,z and a confidence value. 79 | numFeatures = 0; 80 | numFeatures = (buf.limit() / 4); 81 | 82 | // no features in the cloud 83 | if (numFeatures < 1) { 84 | setRenderable(null); 85 | return; 86 | } 87 | 88 | // Each feature point is drawn as a 4 sided pyramid. 89 | // 4 points per feature. 90 | int vertexPerFeature = 4; 91 | int numFaces = 4; 92 | int numPoints = numFeatures * vertexPerFeature; 93 | int vertexPerFace = 3; 94 | 95 | // draw a triangle per face (4 triangles) per feature. 96 | int indexPerFeature = numFaces * vertexPerFace; 97 | 98 | int numIndices = numFeatures * indexPerFeature; 99 | 100 | // allocate a buffer on the high water mark. 101 | if (ptbuffer == null || ptbuffer.length < numPoints) { 102 | ptbuffer = new Vertex[numPoints]; 103 | indexbuffer = new int[numIndices]; 104 | } 105 | 106 | Vector3 feature = new Vector3(); 107 | Vector3[] p = {new Vector3(), new Vector3(), new Vector3(), new Vector3()}; 108 | Vertex.UvCoordinate uv0 = new Vertex.UvCoordinate(0,0); 109 | Vector3 normal0 = new Vector3(0,0,1); 110 | Vector3 normal1 = new Vector3(.7f,0,.7f); 111 | Vector3 normal2 = new Vector3(-.7f,0,.7f); 112 | Vector3 normal3 = new Vector3(0,1,0); 113 | 114 | // Point cloud data is 4 floats per feature, {x,y,z,confidence} 115 | for (int i = 0; i < buf.limit() / 4; i++) { 116 | // feature point 117 | feature.x = buf.get(i * 4); 118 | feature.y = buf.get(i * 4 + 1); 119 | feature.z = buf.get(i * 4 + 2); 120 | 121 | // Top point 122 | p[0].x = feature.x; 123 | p[0].y = feature.y + POINT_DELTA; 124 | p[0].z = feature.z; 125 | 126 | // left pt 127 | p[1].x = feature.x - POINT_DELTA; 128 | p[1].y = feature.y; 129 | p[1].z = feature.z - POINT_DELTA; 130 | 131 | // front point 132 | p[2].x = feature.x; 133 | p[2].y = feature.y; 134 | p[2].z = feature.z + POINT_DELTA; 135 | 136 | // right pt 137 | p[3].x = feature.x + POINT_DELTA; 138 | p[3].y = feature.y; 139 | p[3].z = feature.z - POINT_DELTA; 140 | 141 | int vertexBase = i * vertexPerFeature; 142 | 143 | // Create the vertices. Set the tangent and UV to quiet warnings about material requirements. 144 | ptbuffer[vertexBase] = Vertex.builder().setPosition(p[0]) 145 | .setUvCoordinate(uv0) 146 | .setNormal(normal0) 147 | .build(); 148 | ptbuffer[vertexBase + 1] = Vertex.builder().setPosition(p[1]) 149 | .setUvCoordinate(uv0) 150 | .setNormal(normal1) 151 | .build(); 152 | 153 | ptbuffer[vertexBase + 2] = Vertex.builder().setPosition(p[2]) 154 | .setUvCoordinate(uv0) 155 | .setNormal(normal2) 156 | .build(); 157 | 158 | ptbuffer[vertexBase + 3] = Vertex.builder().setPosition(p[3]) 159 | .setUvCoordinate(uv0) 160 | .setNormal(normal3) 161 | .build(); 162 | 163 | int featureBase = i * indexPerFeature; 164 | 165 | // The indices of the triangles need to be listed counter clockwise as 166 | // appears when facing the front side of the face. 167 | 168 | // left 0 1 2 169 | indexbuffer[featureBase + 2] = vertexBase; 170 | indexbuffer[featureBase] = vertexBase + 1; 171 | indexbuffer[featureBase + 1] = vertexBase + 2; 172 | 173 | // right 0 2 3 174 | indexbuffer[featureBase + 3] = vertexBase; 175 | indexbuffer[featureBase + 4] = vertexBase + 2; 176 | indexbuffer[featureBase + 5] = vertexBase + 3; 177 | 178 | // back 0 3 1 179 | indexbuffer[featureBase + 6] = vertexBase; 180 | indexbuffer[featureBase + 7] = vertexBase + 3; 181 | indexbuffer[featureBase + 8] = vertexBase + 1; 182 | 183 | // bottom 1,2,3 184 | indexbuffer[featureBase + 9] = vertexBase + 1; 185 | indexbuffer[featureBase + 10] = vertexBase + 2; 186 | indexbuffer[featureBase + 11] = vertexBase + 3; 187 | } 188 | 189 | RenderableDefinition.Submesh submesh = 190 | RenderableDefinition.Submesh.builder() 191 | .setName("pointcloud") 192 | .setMaterial(materialHolder.getNow(null)) 193 | .setTriangleIndices( 194 | IntStream.of(indexbuffer) 195 | .limit(numIndices) 196 | .boxed() 197 | .collect(Collectors.toList())) 198 | .build(); 199 | 200 | RenderableDefinition def = 201 | RenderableDefinition.builder() 202 | .setVertices(Stream.of(ptbuffer).limit(numPoints).collect(Collectors.toList())) 203 | .setSubmeshes(Stream.of(submesh).collect(Collectors.toList())) 204 | .build(); 205 | 206 | ModelRenderable.builder().setSource(def).build().thenAccept(renderable -> { 207 | renderable.setShadowCaster(false);setRenderable(renderable);}); 208 | } 209 | } 210 | 211 | 212 | // getter and setter 213 | public int getNumberOfFeatures() { 214 | return numFeatures; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /app/src/main/java/com/pjinkim/arcore_data_logger/WorldToScreenTranslator.java: -------------------------------------------------------------------------------- 1 | package com.pjinkim.arcore_data_logger; 2 | 3 | import android.opengl.Matrix; 4 | 5 | import com.google.ar.core.Camera; 6 | import com.google.ar.core.Pose; 7 | 8 | //Source: https://stackoverflow.com/questions/49026297/convert-3d-world-arcore-anchor-pose-to-its-corresponding-2d-screen-coordinates/49066308#49066308 9 | 10 | public class WorldToScreenTranslator { 11 | private float[] calculateWorld2CameraMatrix(float[] modelmtx, float[] viewmtx, float[] prjmtx) { 12 | 13 | float scaleFactor = 1.0f; 14 | float[] scaleMatrix = new float[16]; 15 | float[] modelXscale = new float[16]; 16 | float[] viewXmodelXscale = new float[16]; 17 | float[] world2screenMatrix = new float[16]; 18 | 19 | Matrix.setIdentityM(scaleMatrix, 0); 20 | scaleMatrix[0] = scaleFactor; 21 | scaleMatrix[5] = scaleFactor; 22 | scaleMatrix[10] = scaleFactor; 23 | 24 | Matrix.multiplyMM(modelXscale, 0, modelmtx, 0, scaleMatrix, 0); 25 | Matrix.multiplyMM(viewXmodelXscale, 0, viewmtx, 0, modelXscale, 0); 26 | Matrix.multiplyMM(world2screenMatrix, 0, prjmtx, 0, viewXmodelXscale, 0); 27 | 28 | return world2screenMatrix; 29 | } 30 | 31 | private double[] world2Screen(int screenWidth, int screenHeight, float[] world2cameraMatrix) 32 | { 33 | float[] origin = {0f, 0f, 0f, 1f}; 34 | float[] ndcCoord = new float[4]; 35 | Matrix.multiplyMV(ndcCoord, 0, world2cameraMatrix, 0, origin, 0); 36 | 37 | ndcCoord[0] = ndcCoord[0]/ndcCoord[3]; 38 | ndcCoord[1] = ndcCoord[1]/ndcCoord[3]; 39 | 40 | double[] pos_2d = new double[]{0,0}; 41 | pos_2d[0] = screenWidth * ((ndcCoord[0] + 1.0)/2.0); 42 | pos_2d[1] = screenHeight * (( 1.0 - ndcCoord[1])/2.0); 43 | 44 | return pos_2d; 45 | } 46 | 47 | public double[] worldToScreen(int width, int height, Camera camera, float[] pos3D){ 48 | 49 | // Get projection matrix. 50 | float[] projmtx = new float[16]; 51 | camera.getProjectionMatrix(projmtx, 0, 0.1f, 100.0f); 52 | 53 | // Get camera matrix and draw. 54 | float[] viewmtx = new float[16]; 55 | camera.getViewMatrix(viewmtx, 0); 56 | 57 | float[] anchorMatrix = new float[16]; 58 | 59 | Pose pose = Pose.makeTranslation(pos3D[0], pos3D[1], pos3D[2]); 60 | pose.toMatrix(anchorMatrix, 0); 61 | 62 | float[] world2screenMatrix = 63 | calculateWorld2CameraMatrix(anchorMatrix, viewmtx, projmtx); 64 | double[] anchor_2d = world2Screen(width, height, world2screenMatrix); 65 | return anchor_2d; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/gruvi_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyojinKim/ARCore-Data-Logger/7ca9ea331313b36cb00ef2e3a28908540ea2b126/app/src/main/res/drawable/gruvi_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sfu_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PyojinKim/ARCore-Data-Logger/7ca9ea331313b36cb00ef2e3a28908540ea2b126/app/src/main/res/drawable/sfu_logo.png -------------------------------------------------------------------------------- /app/src/main/res/font/roboto.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/font/roboto_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 29 | 30 | 39 | 40 | 54 | 55 | 68 | 69 | 82 | 83 | 96 | 97 | 110 | 111 | 124 | 125 | 138 | 139 | 152 | 153 | 162 | 163 |